RSS Feed
更好更安全的互联网
  • Exim Off-by-one(CVE-2018-6789)漏洞复现分析

    2018-04-02

    作者:Hcamael@知道创宇404实验室

    前段时间meh又挖了一个Exim的RCE漏洞[1],而且这次RCE的漏洞的约束更少了,就算开启了PIE仍然能被利用。虽然去年我研究过Exim,但是时间过去这么久了,所以这次复现还是花了大量时间在熟悉Exim源码上。

    本次漏洞复现的过程中,踩了好多坑,实际复现的过程中发现堆块的实际情况无法像meh所说的那样的构造,所以在这部分卡了很久(猜测是因为环境不同的原因),之后决定先理解meh利用的大致思路,然后自己根据实际情况对堆块进行构造,虽然过程艰难,但最终基本算是成功了。

    复现环境搭建

    本次使用的环境和上次大致相同, 首先去github上该漏洞的patch commit[2]

    然后把分支切换到上一个commit

    Makefile仍然使用上次那个:

    然后就是编译安装了:

    启动也是跟上次一样,但是这里有一个坑点,开启debug,输出所有debug信息,不开debug,这些都堆的布局都会有影响。不过虽然有影响,但是只是影响构造的细节,总体的构造思路还是按照meh写的paper中那样。

    本篇的复现,都是基于只输出部分debug信息的模式:

    漏洞复现

    因为我觉得meh的文章中,漏洞原理和相关函数的说明已经很详细,我也没啥要补充的,所以直接写我的复现过程

    STEP 1

    首先需要构造一个被释放的chunk,但是没必要像meh文章说的是一个0x6060大小的chunk,只需要满足几个条件:

    这个chunk要被分为三个部分,一个部分是通过store_get获取,用来存放base64解码的数据,用来造成off by one漏洞,覆盖下一个chunk的size,因为通过store_get获取的chunk最小值是0x2000,然后0x10的堆头和0x10的exim自己实现的堆头,所以是一个至少0x2020的堆块。

    第二部分用来放sender_host_name,因为该变量的内存是通过store_malloc获取的,所以没有大小限制

    第三部分因为需要构造一个fake chunk用来过free的检查,所以也是一个至少0x2020的堆块

    和meh的方法不同,我通过unrecognized command来获取一个0x4041的堆块,然后通过EHLO来释放:

    0x1d15180是通过unrecognized command获取的一个0x4040大小的chunk,在执行完EHLO命令后被释放, 然后0x1d191c0是inuse的sender_host_name,这两部分就构成一个0x6060的chunk

    STEP 2

    现在的情况是sender_host_name位于0x6060大小chunk的最底部,而我们需要把它移到中间

    这部分的思路和meh的一样,首先通过unrecognized command占用顶部0x2020的chunk

    之前的文章分析过,unrecognized command申请内存的大小是ss = store_get(length + nonprintcount * 3 + 1);

    通过计算,只需要让length + nonprintcount * 3 + 1 > yield_lengthstore_get函数就会从malloc中申请一个chunk

    这个时候我们就能使用EHLO释放之前的sender_host_name,然后重新设置,让sender_host_name位于0x6060大小chunk的中部

    STEP 3

    现在我们的堆布局是:

    • 第一块未被使用的0x2020大小的chunk
    • 第二块正在被使用0x2000大小的sender_host_name
    • 第三块未被使用,并且和之后堆块合并, 0x6060大小的chunk

    我们现在再回过头来想想各个chunk的size的设置的问题

    CHUNK 1

    第一个chunk是用来触发off by one漏洞,用来修改第二个CHUNK的size位,只能溢出1byte

    store_get最小分配一个0x2020的chunk,能储存0x2000的数据

    这就导致了,如果按照store_get的最小情况来,只能溢出覆盖掉第二个chunk的pre_size位

    然后因为(0x2008-1)%3==0,所以我们能通过b64decode函数的漏洞申请一个能储存0x2008的数据,size=0x2020的chunk,然后溢出一个字节到下一个chunk的size位

    CHUNK2

    第二块chunk,我们首先需要考虑,因为只能修改一个字节,所以最大只能从0x00扩展到0xf0

    其次,我们假设第二块chunk的原始size=0x2021,然后被修改成0x20f1,我们还需要考虑第二块chunk+0x20f1位置的堆块我们是否可控,因为需要伪造一个fake chunk,来bypass free函数的安全检查。

    经过多次调试,发现当第二块chunk的size=0x2001时,更方便后续的利用

    CHUNK3

    第三个chunk只要求大于一个store_get申请的最小size(0x2020)就行了

    STEP 4

    根据第三步叙述的,我们来触发off by one漏洞

    并且构造在第三块chunk中构造一个fake chunk

    STEP 5

    下一步跟meh一样,通过释放sender_host_name,把一个原本0x2000的chunk扩展成0x20f0, 但是却不触发smtp_reset

    STEP 6

    meh提供了一种不需要泄露地址就能RCE的思路

    exim有一个expand_string函数,当其处理的参数中有${run{xxxxx}}, xxxx则会被当成shell命令执行

    acl_check函数中会对各个命令的配置进行检查,然后把配置信息的字符串调用expand_string函数

    我复现环境的配置信息如下:

    所以我有rcpt, data, auth这三个命令可以利用

    比如0x0000000001cedae0地址当前的内容是:

    当我把该字符串修改为${run{/usr/bin/touch /tmp/pwned}}

    则当我向服务器发送AUTH命令时,exim将会执行/usr/bin/touch /tmp/pwned

    所以之后就是meh所说的利用链:

    修改storeblock的next指针为储存acl_check_xxxx字符串的堆块地址 -> 调用smtp_reset -> 储存acl_check_xxxx字符串的堆块被释放丢入unsortedbin -> 申请堆块,当堆块的地址为储存acl_check_xxxx字符串的堆块时,我们可以覆盖该字符串为命令执行的字符串 -> RCE

    STEP 7

    根据上一步所说,我们首先需要修改next指针,第二块chunk的原始大小是0x2000,被修改后新的大小是0x20f0,下一个storeblock的地址为第二块chunk+0x2000,next指针地址为第二块chunk+0x2010

    所以我们申请一个0x2020的chunk,就能够覆盖next指针:

    这里有一个问题

    第二个chunk在AUTH CRAM-MD5命令执行时就被分配了,所以b64decode的内存是从next_yield获取的

    这样就导致一个问题,我们能通过之前的构造来控制在执行b64decodeyield_length的大小,最开始我的一个思路就是,仍然利用off by one漏洞来修改next,这也是我理解的meh所说的partial write

    但是实际情况让我这个思路失败了

    当前的next指针的值为0x1d171b0,如果利用我的思路是可以修改1-2字节,然而储存acl_check_xxx字符的堆块地址为0x1ced980

    我们需要修改3字节,所以这个思路行不通

    所以又有了另一个思路,因为exim是通过fork起子进程来处理每个socket连接的,所以我们可以爆破堆的基地址,只需要爆破2byte

    STEP 8

    在解决地址的问题后,就是对堆进行填充,然后修改相关acl_check_xxx指向的字符串

    然后附上利用截图:

    总结

    坑踩的挺多,尤其是在纠结meh所说的partial write,之后在github上看到别人公布的exp[3],同样也是使用爆破的方法,所以可能我对partial write的理解有问题吧

    另外,通过与github上的exp进行对比,发现不同版本的exim,acl_check_xxx的堆偏移也有差别,所以如果需要RCE exim,需要满足下面的条件:

    1. 包含漏洞的版本(小于等于commit 38e3d2dff7982736f1e6833e06d4aab4652f337a的版本)
    2. 开启CRAM-MD5认证,或者其他有调用b64decode函数的认证
    3. 需要有该exim的binary来计算堆偏移
    4. 需要知道exim的启动参数

    参考

    1. https://devco.re/blog/2018/03/06/exim-off-by-one-RCE-exploiting-CVE-2018-6789-en/
    2. https://github.com/Exim/exim/commit/cf3cd306062a08969c41a1cdd32c6855f1abecf1
    3. https://github.com/skysider/VulnPOC/tree/master/CVE-2018-6789
    作者:dawu | Categories:安全研究技术分享 | Tags:
  • 从补丁到漏洞分析 –记一次joomla漏洞应急

    2018-02-07

    作者:LoRexxar’@知道创宇404实验室

    2018年1月30日,joomla更新了3.8.4版本,这次更新修复了4个安全漏洞,以及上百个bug修复。

    https://www.joomla.org/announcements/release-news/5723-joomla-3-8-4-release.html

    为了漏洞应急这几个漏洞,我花费了大量的时间分析漏洞成因、寻找漏洞触发位置、回溯逻辑,下面的文章比起漏洞分析来说,更接近我思考的思路,希望能给大家带来不一样的东西。

    背景

    其中的4个安全漏洞包括

    根据更新,我们去到github上的joomla项目,从中寻找相应的修复补丁,可以发现,4个安全漏洞的是和3.8.4的release版同时更新的。

    https://github.com/joomla/joomla-cms/commit/0ec372fdc6ad5ad63082636a0942b3ea39acc7b7

    通过补丁配合漏洞详情中的简单描述我们可以确定漏洞的一部信息,紧接着通过这部分信息来回溯漏洞成因。

    SQLi vulnerability in Hathor postinstall message

    https://developer.joomla.org/security-centre/722-20180104-core-sqli-vulnerability.html

    补丁分析

    第一个漏洞说的比较明白,是说在Hathor的postinstall信息处,由于错误的类型转换导致了注入漏洞。

    我们来看看相应的补丁

    符合漏洞描述的点就是这里,原来的取第一位改为了对取出信息做强制类型转换,然后拼接入sql语句。

    这里假设我们可以控制$adminstyle,如果我们通过传入数组的方式设置该变量为数组格式,并且第1个字符串可控,那么这里就是一个可以成立的漏洞点。

    现在我们需要找到这个功能的位置,并且回溯变量判断是否可控。

    找到漏洞位置

    hathor是joomla自带的两个后台模板之一,由于hathor更新迭代没有isis快,部分功能会缺失,所以在安装完成之后,joomla的模板为isis,我们需要手动设置该部分。

    修改完成后回到首页,右边就是postinstallation message

    回溯漏洞

    回到代码中,我们需要找到$adminstyle这个变量进入的地方。

    这里user为JFactory::getUser(),跟入getParam方法

    这里$this->_params来自$this->_params = new Registry;

    跟入Registry的get方法

    根据这里的调用方式来看,这里会通过这里的的判断获取是否存在adminstyle,如果没有则会返回default(这里为空)

    接着回溯$this->data,data来自$this->data = new \stdClass;

    回溯到这里可以发现$admin_style的地方是从全局变量中中读取的。

    默认设置为空/administrator/components/com_users/models/forms/user.xml

    但我们是可以设置这个的

    后台users->users->super user设置,右边我们可以设置当前账户使用的后台模板,将右边修改为使用hathor型模板。

    通过抓包我们可以发现,这里显式的设置了当前账户的admin_type,这样如果我们通过传入数组,就可以设置admin_type为任意值

    然后进入代码中的数据库操作
    /administrator/templates/hathor/postinstall/hathormessage.php function hathormessage_postinstall_condition

    访问post_install页面触发

    XSS vulnerability in com_fields

    https://developer.joomla.org/security-centre/720-20180102-core-xss-vulnerability.html

    补丁分析

    漏洞详情写了很多,反而是补丁比较模糊,我们可以大胆猜测下,当插入的字段类型为list、radio、checkbox多出的部分变量没有经过转义

    首先我们需要先找到触发点

    后台content->fields->new,然后设置type为radio,在键名处加入相应的payload

    然后保存新建文章

    成功触发

    漏洞分析

    由于补丁修复的方式比较特殊,可以猜测是在某些部分调用时使用了textContent而不是nodeValue,在分析变量时以此为重点。

    漏洞的出发点/administrator/components/com_fields/libraries/fieldslistplugin.php line 31

    由于找不到该方法的调用点,所以我们从触发漏洞的点分析流程。

    编辑文章的上边栏是通过administrator/components/com_content/views/article/tmp/edit.php line 99载入的

    这里JLayoutHelper:render会进入/layouts/joomla/edit/params.php

    然后在129行进入JLayoutHelper::render('joomla.edit.fieldset', $displayData);

    跟入/layouts/joomla/edit/fieldset.php line 16,代码在这里通过执行formgetFieldset获取了提交的自定义字段信息。

    跟入/libraries/src/Form/Form.php line 329 function getFieldset

    跟如1683行 findFieldsByFieldset函数。

    这里调用xml来获取数据,从全局的xml变量中匹配。

    这里的全局变量中的xml中的option字段就来自于设置时的$option->textContent,而只有list, radio and checkbox.这三种是通过这里的函数做处理,其中list比较特殊,在后面的处理过程中,list类型的自定义字段会在/libraries/cms/html/select.php line 742 function options被二次处理,但radio不会,所以漏洞存在。

    整个xss漏洞从插入到触发限制都比较大,实战价值较低。

    XSS vulnerability in Uri class

    https://developer.joomla.org/security-centre/721-20180103-core-xss-vulnerability.html

    补丁分析

    比起其他几个来说,这里的漏洞就属于特别清晰的,就是在获取系统变量时,没做相应的过滤。

    前台触发方式特别简单,因为这里的script_name是获取基础url路径的,会拼接进所有页面的和链接有关系的地方,包括js或者css的引入。

    漏洞利用

    让我们来看看完整的代码

    很明显只有当$script_name = $_SERVER['PHP_SELF']的时候,漏洞才有可能成立

    只有当php是fastcgi运行,而且cgi.fix_pathinfo = 0时才能进入这个判断,然后利用漏洞还有一个条件,就是服务端对路径的解析存在问题才行。

    当该路径能被正常解析时,http://127.0.0.1/index.php/{evil_code}就会被错误的设置为基础URL拼接入页面中。

    一个无限制的xss就成立了

    XSS vulnerability in module chromes

    https://developer.joomla.org/security-centre/718-20180101-core-xss-vulnerability.html

    补丁分析

    漏洞存在的点比较清楚,修复中将$moduleTag进行了一次转义,同样的地方有三处,但都是同一个变量导致的。

    这个触发也比较简单,当我们把前台模板设置为protostar(默认)时,访问前台就会触发这里的modChrome_well函数。

    漏洞利用

    让我们看看完整的代码

    很明显后面module_tag没有经过更多处理,就输出了,假设我们可控module_tag,那么漏洞就成立。

    问题在于怎么控制,这里的函数找不到调用的地方,能触发的地方都返回了传入的第二个值,猜测和上面的get_param一样,如果没有设置该变量,则返回default值。

    经过一番研究,并没有找到可控的设置的点,这里只能暂时放弃。

    ref

    作者:dawu | Categories:安全研究技术分享 | Tags:
  • DeDeCMS v5.7 密码修改漏洞分析

    2018-01-18

    作者:LoRexxar'@知道创宇404实验室

    #### 0x01 背景

    织梦内容管理系统(DedeCms)以简单、实用、开源而闻名,是国内最知名的PHP开源网站管理系统,也是使用用户最多的PHP类CMS系统,在经历多年的发展,目前的版本无论在功能,还是在易用性方面,都有了长足的发展和进步,DedeCms免费版的主要目标用户锁定在个人站长,功能更专注于个人网站或中小型门户的构建,当然也不乏有企业用户和学校等在使用该系统。

    2018年1月10日, 锦行信息安全公众号公开了一个关于DeDeCMS前台任意用户密码修改漏洞的细节\[2]。

    2018年1月10日,Seebug漏洞平台\[3]收录该漏洞,漏洞编号为SSV-97074,知道创宇404漏洞应急团队成功复现该漏洞。

    2018年1月17日,阿里先知平台公开了一个任意用户登陆漏洞\[4]\[5],和一个安全隐患\[6],通过组合漏洞,导致后台密码可以被修改。

    2018年1月18日,知道创宇404漏洞应急团队成功复现该漏洞。

    #### 0x02 漏洞简述

    整个漏洞利用链包括3个过程:

    1. 前台任意用户密码修改漏洞
    2. 前台任意用户登陆漏洞
    3. 前台管理员密码修改可影响后台的安全隐患

    通过3个问题连起来,我们可以重置后台admin密码,如果我们获得了后台地址,就可以进一步登陆后台进行下一步攻击。

    ##### 1、前台任意用户密码修改漏洞
    前台任意用户密码修改漏洞的核心问题是由于DeDeCMS对于部分判断使用错误的弱类型判断,再加上在设置初始值时使用了NULL作为默认填充,导致可以使用弱类型判断的漏洞来绕过判断。

    漏洞利用有几个限制:

    1. 漏洞只影响前台账户 admin账户在前台是敏感词无法登陆
    2. admin账户的前后台密码不一致,无法修改后台密码。
    3. 漏洞只影响未设置密保问题的账户

    ##### 2、前台任意用户登陆漏洞

    前台任意用户登陆漏洞主要是利用了DeDeCMS的机制问题,通过一个特殊的机制,我们可以获得任意通过后台加密过的cookie,通过这个cookie我们可以绕过登陆,实现任意用户登陆。

    漏洞利用有一个限制:

    如果后台开启了账户注册审核,那就必须等待审核通过才能进行下一步利用。

    ##### 3、前台管理员密码修改可影响后台的安全隐患

    在DeDeCMS的设计中,admin被设置为不可从前台登陆,但是当后台登陆admin账户的时候,前台同样会登陆管理员账户。

    而且在前台的修改密码接口,如果提供了旧密码,admin同样可以修改密码,并且这里修改密码会同步给后台账户。

    通过3个漏洞配合,我们可以避开整个漏洞利用下的大部分问题。

    前台任意用户密码修改漏洞->修改admin密码,前台任意用户登录漏洞->登陆admin账户,通过刚才修改的admin密码,来重置admin账户密码。

    #### 0x03 漏洞复现

    ##### 1、 登陆admin前台账户

    安装DeDeCMS
    ![](https://images.seebug.org/content/images/2018/01/5d867e22-6725-44a0-8921-eb1a470accb1.png-w331s)

    注册用户名为000001的账户

    ![](https://images.seebug.org/content/images/2018/01/2c982abb-8803-4ace-b900-b76fdebb1090.png-w331s)

    由于是本地复现漏洞,所以我们直接从数据库中修改为审核通过

    ![](https://images.seebug.org/content/images/2018/01/62afac8d-5130-4df8-9755-b14d505cadc2.png-w331s)

    访问

    http://your_website/member/index.php?uid=0000001

    ![](https://images.seebug.org/content/images/2018/01/f5289b12-2d0c-4d1c-9c7a-f4ad0d1053cd.png-w331s)

    获取cookie中last_vid_ckMd5值,设置DeDeUserID_ckMd5为刚才获取的值,并设置DedeUserID为0000001

    访问

    http://your_website/member/

    ![](https://images.seebug.org/content/images/2018/01/81bdc4e1-6df5-4cb8-be06-1aebe66d7e47.png-w331s)

    ##### 2、修改admin前台登陆密码

    使用DeDeCMS前台任意用户密码修改漏洞修改admin前台密码。

    构造漏洞利用请求


    http://yourwebsite/member/resetpassword.php

    dopost=safequestion&safequestion=0.0&safeanswer=&id=1

    ![](https://images.seebug.org/content/images/2018/01/65169b22-9cca-49a4-b049-6b781721bad5.png-w331s)

    从Burp获取下一步利用链接

    /member/resetpassword.php?dopost=getpasswd&id=1&key=nlszc9Kn

    ![](https://images.seebug.org/content/images/2018/01/01734ff4-d48d-42ca-b5d2-ec8837ee70e9.png-w331s)

    直接访问该链接,修改新密码

    ![](https://images.seebug.org/content/images/2018/01/c0f3ffe5-6eb1-4064-92e4-16c366d84ba8.png-w331s)

    成功修改登陆admin密码

    ##### 3、修改后台密码

    访问


    http://yourwebsite/member/edit_baseinfo.php

    使用刚才修改的密码再次修改密码

    ![](https://images.seebug.org/content/images/2018/01/e77740d5-d2f6-443b-b4db-3ba0dbb7a37b.png-w331s)

    成功登陆

    ![](https://images.seebug.org/content/images/2018/01/c68183da-b541-40e3-86d2-8b75aadb361a.png-w331s)

    #### 0x04 代码分析

    ##### 1、 前台任意用户登陆

    在分析漏洞之前,我们先来看看通过cookie获取登陆状态的代码。


    /include/memberlogin.class.php 161行

    ![](https://images.seebug.org/content/images/2018/01/ca006dbb-b378-48b4-a907-1fb7ea9b3c85.png-w331s)

    通过GetCookie函数从DedeUserID取到了明文的M_ID,通过intval转化之后,直接从数据库中读取该id对应的用户数据。

    让我们来看看GetCookie函数


    /include/helpers/cookie.helper.php 56行

    ![](https://images.seebug.org/content/images/2018/01/0a4a3a7c-3378-4d06-b4d4-e92f6f838185.png-w331s)

    这里的cfg_cookie_encode是未知的,DeDeCMS通过这种加盐的方式,来保证cookie只能是服务端设置的,所以我们没办法通过自己设置cookie来登陆其他账户。

    这里我们需要从别的地方获取这个加密后的值。


    /member/index.php 161行

    ![](https://images.seebug.org/content/images/2018/01/083c37a6-97ff-4655-8c96-0bf481c554e9.png-w331s)

    161行存在一段特殊的代码,这段代码是用来更新最新的访客记录的,当last_vid没有设置的时候,会把userid更新到这个变量中,更新到flag中。

    而这里的userid就是注册时的用户名(如果不是已存在的用户名的话,会因为用户不存在无法访问这个页面)。

    通过这种方式,我们就可以通过已知明文来获取我们想要的密文。

    这里我们通过注册userid为形似00001或者1aaa这样的用户,在获取登陆状态时,mid会经过intval的转化变为1,我们就成功的登陆到admin的账户下。

    ps:该漏洞影响所有用户

    ##### 2、前台任意用户密码修改

    漏洞主要逻辑在 /member/resetpassword.php 75行至95行

    ![](https://images.seebug.org/content/images/2018/01/80dc5c44-533f-4eb2-8dae-c75f255a936b.png-w331s)

    当找回密码的方式为安全问题时

    dedecms会从数据库中获取用户的安全问题、回答进行比对,当我们在注册时没设置安全问题时。

    从数据库中可以看到默认值为NULL(admin默认没有设置安全问题)

    ![](https://images.seebug.org/content/images/2018/01/33dd3447-bc19-4fc4-a869-f3961a67bd55.png-w331s)

    下面是设置了安全问题时数据库的样子,safequestion代表问题的id,safeanswer代表安全问题的回答。

    我们需要绕过第一个判断

    if(empty($safequestion)) $safequestion = '';

    这里我们只要传入0.0就可以绕过这里,然后0.0 == 0为True,第二个判断NULL==""为True,成功进入sn函数。

    跟入/member/inc/inc_pwd_functions.php 第150行

    ![](https://images.seebug.org/content/images/2018/01/005be5b8-87cc-433d-8b39-2f7dadeec734.png-w331s)

    有效时间10分钟,进入newmail函数

    跟入/member/inc/inc_pwd_functions.php 第73行

    ![](https://images.seebug.org/content/images/2018/01/23657efd-40df-4619-a322-89d3d68974c9.png-w331s)

    77行通过random生成了8位的临时密码。

    这里我们使用的是安全问题修改密码,所以直接进入94行,将key代入修改页。

    跳转进入形似

    /member/resetpassword.php?dopost=getpasswd&id=1&key=nlszc9Kn

    的链接,进入修改密码流程

    唯一存在问题的是,这里&错误的经过一次编码,所以这里我们只能手动从流量中抓到这个链接,访问修改密码。

    ##### 3、修改后台密码安全隐患

    在DeDeCMS的代码中,专门对前台修改管理员密码做了设置,如果是管理员,则一并更新后台密码,也就是这个安全隐患导致了这个问题。


    /member/edit_baseinfo.php 119行

    ![](https://images.seebug.org/content/images/2018/01/921e15f3-9e3e-4a34-a1f3-54475a6f25eb.png-w331s)

    #### 0x05 修复方案

    截至该文章完成时,DeDeCMS的官方仍然没有修复该漏洞,所以需要采用临时修复方案,等待官方正式修复更新。

    由于攻击漏洞涉及到3个漏洞,但官方仍然没有公开补丁,所以只能从一定程度上减小各个漏洞的影响。

    - 前台任意用户登陆漏洞:开启新用户注册审核,当发现userid为1xxxx或1时,不予以
    通过审核。

    在官方更新正式补丁之前,可以尝试暂时注释该部分代码,以避免更大的安全隐患

    /member/index.php 161-162行

    ![](https://images.seebug.org/content/images/2018/01/f88b5b3e-567b-4f72-a3cb-5efb4a7a18cb.png-w331s)

    - 前台修改后台管理员密码:设置较为复杂的后台地址,如果后台地址不可发现,则无法登陆后台。
    - 前台任意用户密码修改漏洞:

    修改文件/member/resetpassword.php 第84行
    ![](https://images.seebug.org/content/images/2018/01/7b8c7600-68dc-4eb9-bfc5-bfee00d158b7.png-w331s)

    将其中的==修改为===

    ![](https://images.seebug.org/content/images/2018/01/920c7470-c038-44a7-b43d-12c27a8f5a87.png-w331s)

    即可临时防护该该漏洞。

    #### 0x06 ref

    [1] DeDeCMS官网

    [2] 漏洞详情原文

    [3] Seebug漏洞平台

    [4] 阿里先知平台漏洞分析1

    [5] 阿里先知平台漏洞分析2

    [6] 漏洞最早分析原文

    作者:dawu | Categories:安全研究技术分享 | Tags:
  • 如何通过TTL调试光猫

    2018-01-17

    作者:Sebao@知道创宇404实验室

    序言

    众所周知,光猫是现在每个家庭必备的一款设备,但是光猫背面写的账号密码,只是普通用户权限,会限制很多功能。这篇文章讲述,如何通过TTL调试的方法获取光猫超级管理员的权限。

    0x00 名词解释

    引脚介绍(COM口pin比较多,但是常用的也是这几个):

    VCC:供电pin,一般是3.3v,在我们的板子上没有过电保护,这个pin一般不接更安全

    GND:接地pin,有的时候rx接受数据有问题,就要接上这个pin,一般也可不接

    RX:接收数据pin

    TX:发送数据pin,我之前碰到串口只能收数据,不能发数据问题,经baidu,原来是设置了流控制,取消就可以了,适用于putty,SecureCRT

    在调试的时候, 多数情况下我们只引出rx,tx即可.

    0x01 所需工具:

    1,万用表

    2,TTL转USB版

    3,电烙铁

    4,杜邦线若干只

    5,SecureCRT

    0x02 华为光猫

    TTL调试的第一步骤就是拆机,拆机步骤这里就不详细描述。这里先看一下拆下来的光猫板子是什么样子的。

    TTL调试我们首先要找出 GND,RX,TX。从图中可以看到,已经标识出了 GND,RX,TX的接口,就需要通过USB转TTL小板串口读取固件。 查找GND,可以用万用表查找。

    用杜邦线连接到板子上,线序为GND接GND,RXD接TTL板的TXD,TXD 接TTL板的RXD。

    USB端连接上电脑,在控制面板,设备管理器查看串口(一般在COM1-COM12之间),Connection type设置为:Serial,Serial line设置为你电脑上显示的串口,Speed设置为115200,然后连接。接通电源后等待,在这一段时间内串口应该会打印很多启动信息,启动差不多后,敲回车:

    然后输入默认的账号root 密码 admin登录进去,输入shell命令,进入busybox.看一下此设备的cpu架构,用的是ARM7.

    准备查找超级管理员的密码。进入/mnt/jffs2目录,复制配置文件hw_ctree.xml到myconf.xml.gz中。这个文件是AES加密的,所以先解密,命令为aescrypt2 1 myconf.xml.gz tmp

    解密后的文件还是被压缩了的,所以要用gzip命令展开压缩文件myconf.xml.gz,得到myconf.xml。命令为:gzip -d myconf.xml.gz

    然后用 grep 命令 查找telecomadmin,也就是超级管理员的密码。命令为:grep telecomadmin myconf.xml

    0x03 烽火光猫

    和上述步骤一样,首先拆开光猫找到 GND,RX,TX。这个板子人性化的已经标明了GND,RX,TX。

    所以直接用杜邦线连接到板子上,线序为GND接GND,RXD接TTL板的TXD,TXD 接TTL板的RXD。

    USB端连接上电脑。

    接通电源后等待,在这一段时间内串口应该会打印很多启动信息,这里直接CTRL+C 跳过直接进入shell模式,这个也算是一个“后门”。输入命令 cat proc/cpuinfo查看cpu的架构。

    进入shell获取超级管理员的方法差不多,参考上文即可,这里不再详细描述。

    0x04 长虹光猫

    和上述步骤一样。

    因为这里没有针孔,所以需要焊接杜邦线到板子上,以便于固定杜邦线。

    USB端连接上电脑。

    0x05 总结

    感谢 知道创宇404实验室 dawu,fenix提供的思路以及技巧。

    作者:dawu | Categories:安全研究技术分享 | Tags:
  • Huawei HG532 系列路由器远程命令执行漏洞分析

    2017-12-28

    作者:fenix@知道创宇404实验室
    English version: https://paper.seebug.org/508/

    背景

    华为 HG532 系列路由器是一款为家庭和小型办公用户打造的高速无线路由器产品。

    2017/11/27,Check Point 软件技术部门报告了一个华为 HG532 产品的远程命令执行漏洞(CVE-2017-17215)【1】

    该漏洞在被报告前,互联网上产生了大量未被关注的此类漏洞攻击利用包,遍及全球多个国家。Payload 已被证实是知名病毒 Mirai 的升级版变种 OKIRU/SATORI。该 Payload 功能非常简单,主要通过发送精心制作的 UDP/TCP 报文来对目标发起 DDoS 攻击。

    2017/11/23,知道创宇 404 实验室的 ZoomEye 网络探针系统也捕获到了该攻击的 Payload。

    漏洞分析

    固件下载

    网上有 HG532e 版本的公开固件,下载地址【2】

    下载该固件,利用 binwalk 直接解压。

    目标系统是 MIPS 32 位 大端架构。

    漏洞分析

    根据 Check Point 的报告【1】,该远程命令执行漏洞的漏洞点位于 UPnP 服务中。

    UPnP 是由“通用即插即用论坛”(UPnP™ Forum)推广的一套网络协议。该协议的目标是使家庭网络(数据共享、通信和娱乐)和公司网络中的各种设备能够相互无缝连接,并简化相关网络的实现。

    直接将固件中负责 UPnP 服务的 upnp 程序扔到 IDA。

    通过字符串 NewStatusURL 对漏洞点进行定位。

    跟踪数据交叉引用

    漏洞点如下

    ATP_XML_GetChildNodeByName 函数的定义如下。

    程序首先进行 SOAP XML 报文解析,得到元素 NewDownloadURL 和 NewStatusURL 的值。然后进行以下拼接,最终调用 system() 函数执行。

    upg 是路由器的一个升级程序,他的参数功能如下。

    现在我们有两个命令注入点,NewDownloadURLNewStatusURL

    漏洞验证

    目标系统提供了以下命令。

    利用 wget 命令进行漏洞测试。发送以下报文。

    可以看到,我们成功在监听的端口上收到了请求。

    值得一提的是,HG532e 路由器的 uPnP 服务和防火墙都是默认开启的,防火墙默认等级为低级。

    在默认设置下,从 WAN 口访问 37215 端口会被防火墙拦截,漏洞无法被利用。

    防护方案

    2017/11/30,华为官方发布了安全公告【4】,确认了该漏洞。
    公告中提到了以下漏洞缓解措施

    • 配置路由器内置的防火墙
    • 更改路由器默认密码
    • 在路由器外部署防火墙

    是的,没找到固件升级包,所以,没有补丁分析…

    总结

    1. 和爱尔兰宽带路由器 SetNTPServers 命令注入【3】类似,这个漏洞整体来看就是一个简单的命令拼接。
    2. 该漏洞也为我们漏洞挖掘提供了一个很好的方向。snprintf()、system() 等函数附近的程序逻辑都应该被重点关注。
    3. 还是那句话,一切进入函数的变量都是有害的。大部分远程命令执行漏洞要么是过滤不全,导致命令拼接。要么是没有进行变量长度控制,造成缓冲区溢出。关于这点设备供应商应该负责任,安全开发意识非常重要。

    参考链接

    【1】 Check Point 漏洞报告
    https://research.checkpoint.com/good-zero-day-skiddie/
    【2】 HG532e 固件下载
    https://ia601506.us.archive.org/22/items/RouterHG532e/router%20HG532e.rar
    【3】 爱尔兰宽带路由器 SetNTPServers 命令注入
    https://www.seebug.org/vuldb/ssvid-97024/https://www.seebug.org/vuldb/ssvid-97024
    【4】 华为安全公告
    http://www.huawei.com/en/psirt/security-notices/huawei-sn-20171130-01-hg532-en/http://www.huawei.com/en/psirt/security-notices/huawei-sn-20171130-01-hg532-en

    作者:dawu | Categories:安全研究技术分享 | Tags:
  • Vivotek 摄像头远程栈溢出漏洞分析及利用

    2017-12-14

    作者:fenix@知道创宇404实验室

    前言

    近日,Vivotek 旗下多款摄像头被曝出远程未授权栈溢出漏洞,攻击者发送特定数据可导致摄像头进程崩溃。

    漏洞作者@bashis 放出了可造成摄像头 Crash 的 PoC :https://www.seebug.org/vuldb/ssvid-96866/https://www.seebug.org/vuldb/ssvid-96866

    该漏洞在 Vivotek 的摄像头中广泛存在,按照官方的安全公告,会影响以下版本

    Vivotek 官方提供了各种型号摄像头的固件下载:http://www.vivotek.com/firmware/ ,这也为我们的研究带来了很多便利。

    我们发现,漏洞被曝出之后,在官网固件下载页面中的大多数固件均早于漏洞曝出时间,我们下载了几款摄像头的最新固件进行验证,发现漏洞依然存在,这意味着截止漏洞被曝出,Vivotek 官方对该漏洞的修复并不彻底。众所周知,栈溢出是存在潜在的远程命令执行风险的,为了深入了解该漏洞的影响,我们决定研究下该漏洞的原理及利用。

    调试环境搭建

    固件下载

    由于手头上并没有 Vivotek 的摄像头,我们在官网下载其中一款摄像头固件,使用 qemu 模拟运行。(注:官方在陆续发布各个版本的固件更新,可根据固件发布时间判断官方是否已经修复漏洞)

    首先下载摄像头固件:http://download.vivotek.com/downloadfile/downloads/firmware/cc8160firmware.zip/http://download.vivotek.com/downloadfile/downloads/firmware/cc8160firmware.zip

    通过 binwalk 直接解压出其中的文件系统,和漏洞有关的主要文件如下

    根据 file 命令的结果可知目标架构为 ARM、小端、32位。且该 ELF 文件为动态链接。

    修复运行依赖

    尝试用 qemu 运行,结果如下

    服务没有运行起来,且没有明显的报错,猜想到可能是缺少某些依赖,程序直接退出了,扔到 IDA,从程序退出前的提示:gethostbyname:: Success,回溯程序异常退出原因。

    依次加载IDA 菜单栏 -> View -> Open subviews -> Strings,Command + F 搜索 gethostname

    查看交叉引用信息,定位相应代码段

    异常退出部分代码如下

    为了看的更直观,我们来贴一下 F5 的结果,如下

    这部分主要涉及两个函数。gethostname():返回本地主机的标准主机名,如果函数成功,则返回 0。如果发生错误则返回 -1。gethostbyname():用域名或主机名获取IP地址。

    Linux 操作系统的 hostname 是一个 kernel 变量,可以通过 hostname 命令来查看本机的 hostname。也可以直接 cat /proc/sys/kernel/hostname 查看。

    我们只需要将二者改成一致,httpd 服务即可成功运行。

    调试环境

    为了方便调试,还需要搭建 qemu 虚拟机环境。

    qemu 镜像文件下载:https://people.debian.org/~aurel32/qemu/armel/ (下载内核 3.2 的版本)

    远程调试 gdbserver:https://github.com/mzpqnxow/gdb-static-cross/tree/master/prebuilt-static/https://github.com/mzpqnxow/gdb-static-cross/tree/master/prebuilt-static

    qemu 虚拟机建议采用 桥接 方式和主机连接。

    启动虚拟机,进行简单配置等待远程调试。

    漏洞研究

    定位溢出点

    以下为漏洞作者 @bashis 提供的 PoC

    老套路, 根据 Content-Length 很容易定位到溢出点,如下

    惊讶到了,strncpy() 函数的长度参数竟然这么用,妥妥的溢出。

    调用栈布局

    dest 缓冲区起始地址距离栈底 0x38 字节,栈上依次为 LR、R11-R4。Content-Length 长度超过 0x38 - 4 字节就会覆盖函数的返回地址 LR。

    exp 研究

    strncpy() 函数引起的栈溢出,在利用时就会有很 egg hurt 的 0x00 坏字符问题,如果我们的输入数据中包含 0x00,将会被截断导致漏洞利用失败。根据溢出点附近的汇编代码来看,0x0a 也会被截断。且开启了 NX 保护,这意味着我们无法在栈上部署 shellcode

    尝试通过 return2libc 的方式 getshell。由于没有实际的摄像头,我们不知道目标系统是否开启了 ASLR ,如果 ASLR 是开启的且没有其它可用来暴露 libC 动态链接库内存地址的漏洞,那么利用该漏洞将会是一个很难受的过程。

    采用以下方式暂时关闭 ASLR

    libC 库的加载地址如下

    接下来就需要精心构造数据,劫持函数的执行流程了。有一点需要注意,X86 架构下的所有参数都是通过堆栈传递的,而在 MIPS 和 ARM 架构中,会优先通过寄存器传递参数,如果参数个数超过了寄存器的数量,则将剩下的参数压入调用参数空间(即堆栈)。

    从前面的分析来看,只要我们构造 0x38 - 4 字节以上的数据,栈底的函数返回地址就会被我们劫持。system() 函数地址 = libC 库在内存中的加载基址 + system() 函数在 libC 库中的偏移,通过劫持该地址为 libC 库中的 system() 函数地址,再设置 R0 寄存器指向命令字符串,就可以执行任意命令。

    经过验证,nc 命令可以正常使用。

    接下来我们开始构造 ROP 利用链,大致思路见以下汇编代码。

    Github 上有个很赞的项目:https://github.com/JonathanSalwan/ROPgadget/https://github.com/JonathanSalwan/ROPgadget

    它可以用来搜索 ELF 文件中的 gadgets,方便我们构造 ROP 链。

    我们需要将字符串参数 nc -lp2222 -e/bin/sh 部署到栈上,并且将地址存入 R0。该参数包含 20 个字节,且不含坏字符。

    libC 基址为 0xb6f2d000,由该地址可知 gadget 在内存中的有效地址。发生溢出时栈顶地址为 0xbeffeb50

    利用 ROPgadget 搜索可用的 gadgets,在选择 gadget 时要还考虑坏字符的问题。比如说如下的 gadget 就不得行。

    再搜索一条可用的 gadget,俗称曲线救国。

    选择以下两条 gadget,构造 ROP 如下。

    通过调试 ,我们可以获得崩溃时的栈顶地址,为了确保命令能执行,我们在真正要执行的命令前加了部分命令作为缓冲。

    可以看到,开启了 NX 保护的栈上虽然不可执行代码,但是依然可以在上面部署数据。我们只需要将要执行的命令部署到栈上,构造 ROP 让 R0 寄存器指向栈上的命令所在区域,然后 return2libC 调用系统函数,就可以执行任意命令了。

    已将 PoC 和 EXP 整理成 Pocsuite 脚本:https://www.seebug.org/vuldb/ssvid-96866/https://www.seebug.org/vuldb/ssvid-96866,验证效果如下。

    致谢

    第一次接触 ARM 汇编,有很多不足之处,欢迎各大佬指正。中途踩了不少坑,感谢 404 小伙伴 @Hcamael 和 @没有ID 的各种疑难解答。

    参考链接

    作者:dawu | Categories:安全研究技术分享 | Tags:
  • CVE-2017-16943 Exim UAF漏洞分析–后续

    2017-12-13

    作者:Hcamael@知道创宇404实验室

    上一篇分析出来后,经过@orange的提点,得知了meh公布的PoC是需要特殊配置才能触发,所以我上一篇分析文章最后的结论应该改成,在默认配置情况下,meh提供的PoC无法成功触发uaf漏洞。之后我又对为啥修改了配置后能触发和默认情况下如何触发漏洞进行了研究

    重新复现漏洞

    比上一篇分析中复现的步骤,只需要多一步,注释了/usr/exim/configure文件中的control = dkim_disable_verify

    然后调整下poc的padding,就可以成功触发UAF漏洞,控制rip

    分析特殊配置下的触发流程

    在代码中有一个变量是dkim_disable_verify, 在设置后会变成true,所以注释掉的情况下,就为默认值false, 然后再看看receive.c中的代码:

    进入了dkim_exim_verify_init函数,之后的大致流程:

    在上一篇文章中说过了,无法成功触发uaf漏洞的原因是,被free的堆处于堆顶,释放后就和top chunk合并了。

    在注释了dkim的配置后,在dkim_exim_verify_init 函数的流程中,执行了一个store_get 函数,申请了一个0x4000大小的堆,然后在dkim_exim_verify_init 函数和dkim_exim_verify_feed 函数中,都有如下的代码:

    store_pool全局变量被修改为了1,之前说过了,exim自己实现了一套堆管理,当store_pool不同时,相当于对堆进行了隔离,不会影响receive_msg 函数中使用堆管理时的current_block这类的堆管理全局变量

    当dkim相关的代码执行结束后,还把store_pool恢复回去了

    因为申请了一个0x4000大小的堆,大于0x2000,所以申请之后yield_length全局变量的值变为了0,导致了之后store_get(0x64)再次申请了一块堆,所以有了两块堆放在了heap1的上面,释放heap1后,heap1被放入了unsortbin,成功触发了uaf漏洞,造成crash。(之前的文章中都有写到)

    默认配置情况下复现漏洞

    在特殊配置情况下复现了漏洞后,又进行了如果在默认配置情况下触发漏洞的研究。

    @explorer大佬的教导下,发现了一种在默认情况下触发漏洞的情况。

    其实触发的关键点,就是想办法在heap1上面再malloc一个堆,现在我们从头来开始分析

    首先,当有新连接进来的时候,fork一个子进程,然后进入上面代码中的那个分支,smtp_setup_msg函数是用来接收命令的函数,我们先发一堆无效的命令过去(padding),控制yield_length的值小于0x100,目的上一篇文章说过了,因为命令无效,流程再一次进入了smtp_setup_msg

    这时候我们发送一个命令BDAT 16356

    然后有几个比较重要的操作:

    首先是把输入的16356赋值给chunking_data_left

    然后把receive_getc换成bdat_getc函数

    再做完这些的操作后,进入了receive_msg函数,按照上篇文章的流程差不多,显示申请了一个0x100的heap1

    然后进入receive_getc=bdat_getc读取数据:

    lwr_receive_getc=smtp_getc通过该函数获取16356个字符串

    首先,我们发送16352个a作为padding,然后执行了下面这流程:

    • store_extend return 0 -> store_get -> store_release

    先申请了一个0x4010的heap2,然后释放了长度为0x2010的heap1

    然后发送:\r\n,进入下面的代码分支:

    跳到了EOL,最重要的是最后几行代码:

    把一些变量重新进行了初始化,因为之前因为padding执行了store_get(0x4000),所以这个时候yield_length=0 这个时候再次调用store_get将会申请一个0x2000大小堆,从unsortbin中发现heap1大小正好合适,所以这个时候得到的就是heap1,在heap1的顶上有一个之前next->text使用,大小0x4010,未释放的堆。

    之后流程的原理其实跟之前的差不多,PoC如下:

    exp

    根据该CVE作者发的文章,得知是利用文件IO的fflush来控制第一个参数,然后通过堆喷和内存枚举来来伪造vtable,最后跳转到expand_string函数来执行命令,正好我最近也在研究ctf中的_IO_FILE的相关利用(之后应该会写几篇这方面相关的blog),然后实现了RCE,结果图如下:


    参考链接

    1. https://devco.re/blog/2017/12/11/Exim-RCE-advisory-CVE-2017-16943-en/
    作者:dawu | Categories:安全研究技术分享 | Tags:
  • CVE-2017-16943 Exim UAF漏洞分析

    2017-12-01

    作者:Hcamael@知道创宇404实验室

    感恩节那天,meh在Bugzilla上提交了一个exim的uaf漏洞:https://bugs.exim.org/show_bug.cgi?id=2199,这周我对该漏洞进行应急复现,却发现,貌似利用meh提供的PoC并不能成功利用UAF漏洞造成crash

    漏洞复现

    首先进行漏洞复现

    环境搭建

    复现环境:ubuntu 16.04 server

    然后再修改下配置文件/etc/exim/configure文件的第364行,把
    accept hosts = : 修改成 accept hosts = *

    PoC测试

    https://bugs.exim.org/attachment.cgi?id=1050获取到meh的debug信息,得知启动参数:

    PoC有两个:

    1. https://bugs.exim.org/attachment.cgi?id=1049
    2. https://bugs.exim.org/attachment.cgi?id=1052

    需要先安装下pwntools,直接用pip装就好了,两个PoC的区别其实就是padding的长度不同而已

    然后就使用PoC进行测试,发现几个问题:

    1. 我的debug信息在最后一部分和meh提供的不一样
    2. 虽然触发了crash,但是并不是UAF导致的crash

    debug信息不同点比较:

    发现的确是抛异常了,但是跟meh的debug信息在最后却不一样,然后使用gdb进行调试,发现:

    根本就不是meh描述的利用UAF造成的crash,继续研究,发现如果把debug all的选项-d+all换成只显示简单的debug信息的选项-dd,则就不会抛异常了

    又仔细读了一遍meh在Bugzilla上的描述,看到这句,所以猜测有没有可能是因为padding大小的原因,才导致crash失败的?所以写了代码对padding进行爆破,长度从0-0x4000,爆破了一遍,并没有发现能成功造成crash的长度。

    This PoC is affected by the block layout(yield_length), so this line: r.sendline('a'*0x1250+'\x7f') should be adjusted according to the program state.

    所以可以排除是因为padding长度的原因导致PoC测试失败。

    而且在漏洞描述页,我还发现Exim的作者也尝试对漏洞进行测试,不过同样测试失败了,还贴出了他的debug信息,和他的debug信息进行对比,和我的信息几乎一样。(并不知道exim的作者在得到meh的Makefile和log后有没有测试成功)。

    所以,本来一次简单的漏洞应急,变为了对该漏洞的深入研究

    浅入研究

    UAF全称是use after free,所以我在free之前,patch了一个printf:

    重新编译跑一遍,发现竟然成功触发了uaf漏洞:

    然后gdb调试的信息也证明成功利用uaf漏洞造成了crash:

    PS: 这里说明下./build-Linux-x86_64/exim这个binary是没有patch printf的代码,/usr/exim/bin/exim是patch了printf的binary

    到这里就很奇怪了,加了个printf就能成功触发漏洞,删了就不能,之后用putswrite代替了printf进行测试,发现puts也能成功触发漏洞,但是write不能。大概能猜到应该是stdio的缓冲区机制的问题,然后继续深入研究。

    深入研究

    来看看meh在Bugzilla上对于该漏洞的所有描述:

    在这里先提一下,在Exim中,自己封装实现了一套简单的堆管理,在src/store.c中

    UAF漏洞所涉及的关键函数:

    • store_get_3 堆分配
    • store_extend_3 堆扩展
    • store_release_3 堆释放

    还有4个重要的全局变量:

    • chainbase
    • next_yield
    • current_block
    • yield_length
    第一步

    发送一堆未知的命令去调整yield_length的值,使其小于0x100。

    yield_length表示的是堆还剩余的长度,每次命令的处理使用的是src/receive.c代码中的receive_msg函数

    在该函数处理用户输入的命令时,使用next->text来储存用户输入,在1709行进行的初始化:

    在执行1709行代码的时候,如果0x100 > yield_length则会执行到newblock = store_malloc(mlength);,使用glibc的malloc申请一块内存,为了便于之后的描述,这块内存我们称为heap1。

    根据store_get_3中的代码,这个时候:

    • current_block->next = heap1 (因为之前current_block==chainbase,所以这相当于是chainbase->next = heap1)
    • current_block = heap1
    • yield_length = 0x2000
    • next_yield = heap1+0x10
    • return next_yield
    • next_yield = next_yield+0x100 = heap1+0x110
    • yield_length = yield_length - 0x100 = 0x1f00
    第二步

    发送BDAT 1,进入receive_msg函数,并且让receive_getc变为bdat_getc

    第三步

    发送BDAT \x7f

    相关代码在src/smtp_in.c中的bdat_getc函数:

    BDAT命令进入下面这个分支:

    因为\x7F 所以sscanf获取长度失败,进入synprot_error函数,该函数同样是位于smtp_in.c文件中:

    然后在synprot_error函数中有一个string_printing函数,位于src/string.c代码中:

    string_printing2函数中,用到store_get, 长度为length + nonprintcount * 3 + 1,比如BDAT \x7F这句命令,就是6+1*3+1 => 0x0a,我们继续跟踪store中的全局变量,因为0xa < yield_length,所以直接使用的Exim的堆分配,不会用到malloc,只有当上一次malloc 0x2000的内存用完或不够用时,才会再进行malloc

    • 0xa 对齐-> 0x10
    • return next_yield = heap1+0x110
    • next_yield = heap1+0x120
    • yield_length = 0x1f00 - 0x10 = 0x1ef0

    最后一步,就是PoC中的发送大量数据去触发UAF:

    再回到receive.c文件中,读取用户输入的是1788行的循环,然后根据meh所说,UAF的触发点是下面这几行代码:

    当输入的数据大于等于0x100-4时,会触发store_extend函数,next->text的值上面提了,是heap1+0x10oldsize=0x100, header_size = 0x100*2 = 0x200

    然后在store_extend中,有这几行判断代码:

    其中next_yield = heap1+0x120, ptr + 0x100 = heap1+0x110

    因为判断的条件为true,所以store_extend返回False

    这是因为在之前string_printing函数中中分配了一段内存,所以在receive_msg中导致堆不平衡了,

    随后进入分支会修补这种不平衡,执行store_get(0x200)

    • return next_yield = heap1+0x120
    • next_yield = heap1+0x320
    • yield_length = 0x1ef0 - 0x200 = 0x1cf0

    然后把用户输入的数据复制到新的堆中

    随后执行store_release函数,问题就在这里了,之前申请的0x2000的堆还剩0x1cf0,并没有用完,但是却对其执行glibc的free操作,但是之后这个free后的堆却仍然可以使用,这就是我们所知的UAF, 释放后重用漏洞

    其中,bb = chainbase->next = heap1, 而且next->text == bb + 0x10

    所以能成功执行free(bb)

    因为输入了大量的数据,所以随后还会执行:

    • store_extend(next->text, 0x200, 0x400)
    • store_extend(next->text, 0x400, 0x800)
    • store_extend(next->text, 0x800, 0x1000)

    但是这些都不能满足判断:if (CS ptr + rounded_oldsize != CS (next_yield[store_pool]) || inc > yield_length[store_pool] + rounded_oldsize - oldsize)

    所以都是返回true,不会进入到下面分支

    但是到store_extend(next->text, 0x1000, 0x2000)的时候,因为满足了第二个判断0x2000-0x1000 > yield_length[store_pool], 所以又一次返回了False

    所以再一次进入分支,调用store_get(0x2000)

    因为0x2000 > yield_length所以进入该分支:

    这里就是该漏洞的关键利用点

    首先:newblock = current_block = heap1

    然后:newblock = newblock->next

    我猜测的meh的情况和我加了printf进行测试的情况是一样的,在printf中需要malloc一块堆用来当做缓冲区,所以在heap1下面又多了一块堆,在free了heap1后,heap1被放入了unsortbin,fd和bk指向了arena

    所以这个时候,heap1->next = fd = arena_top

    之后的流程就是:

    • current_block = arena_top
    • next_yield = arena_top+0x10
    • return next_yield = arena_top+0x10
    • next_yield = arena_top+0x2010

    在执行完store_get后就是执行memcpy:

    上面的newtext就是store_get返回的值arena_top+0x10

    把用户输入的数据copy到了arena中,最后达到了控制RIP=0xdeadbeef造成crash的效果

    但是实际情况就不一样了,因为没有printf,所以heap1是最后一块堆,再free之后,就会合并到top_chunk中,fd和bk字段不会被修改,在释放前,这两个字段也是用来储存storeblock结构体的next和length,所以也是没法控制的

    总结

    CVE-2017-16943的确是一个UAF漏洞,但是在我的研究中却发现没法利用meh提供的PoC造成crash的效果

    之后我也尝试其他利用方法,但是却没找到合适的利用链

    发现由于Exim自己实现了一个堆管理,所以在heap1之后利用store_get再malloc一块堆是不行的因为current_block也会被修改为指向最新的堆块,所以必须要能在不使用store_get的情况下,malloc一块堆,才能成功利用控制RIP,因为exim自己实现了堆管理,所以都是使用store_get来获取内存,这样就只能找printf这种有自己使用malloc的函数,但是我找到的这些函数再调用后都会退出receive_msg函数的循环,所以没办法构造成一个利用链

    引用

    1. Exim源码
    2. Bugzilla-2199
    作者:dawu | Categories:安全研究技术分享 | Tags:
  • TP-LINK WR941N路由器研究

    2017-11-10

    作者:Hcamael@知道创宇404实验室

    之前看到了一个CVE, CVE-2017-13772

    是TP-Link WR940N后台的RCE, 手头上正好有一个TP-Link WR941N的设备,发现也存在相同的问题,但是CVE-2017-13772文章中给的EXP并不通用

    所以准备进行复现和exp的修改,折腾了将近4天,记录下过程和遇到的坑

    第一次研究mips指令的RCE,之前只学了intel指令集的pwn,所以进度挺慢的

    Day 1

    第一天当然是配环境了,该路由器本身在默认情况下是不提供shell的,在@fenix帮助下获取到了路由器的shell,该款路由器上的busybox的命令比较少,curl, nc, wget这些命令都没有,只能用tftp进行数据传输,而且只有/tmp目录可写,路由器重启后,传上去的文件就没了,这些问题都可以通过刷固件解决,不过太麻烦了,只需要传上去一个gdbserver就好了,能根据固件中的bin得知这是一个大端mips指令集的设备,gdbserver也不用自己编译,直接下编译好的: https://github.com/rapid7/embedded-tools/tree/master/binaries/gdbserver

    gdbserver.mipsbe通过tftp上传到路由器的/tmp目录下

    然后根据cve-2017-13772分析文章说的那样使用gdbserver attach httpd最新的一个进程,然后就可以进行远程gdb调试了

    Day 2

    第二天准备开始调试,但是发现gdb的两个编译选项, 一个--host,表示gdb运行的环境,一般默认就是本机环境,还有一个--target表示调试的目标环境,默认也是本机环境,所以一个64位ubuntu上默认的gdb只能调试64 elf程序。所以需要设置--target=mipsbel-linux参数进行编译gdb,才能调试大端的mips程序。

    编译差不多编译了半天,准备改天搞一个8核的机器专门来编译程序….

    编译成功后,就可以进行远程调试了,在路由器上执行:

    然后使用编译好gdb进行调试:

    但是失败了,又折腾了半天

    Day 3

    第三天才真正的开始调试程序,首先说说我第二天遇到的问题,问题是下了断点没用,原因比较傻逼,我下断点的地址是wr940n的地址,我把两个bin搞混了

    然后根据cve-2017-13772分析文章中说的栈溢出的指令,在wr941n中也找到了该指令,而溢出情况也是一样,所以拿了wr940n的exp来打了一遍,结果当然是失败了。

    在wr940n的exp中,ROP是在libuClibc-0.9.30.so中找的,根据$ cat /proc/pid/maps命令,发现wr941n路由器的基地址和文章中显示的wr940n路由器的是一样的,然后再比较libuClibc-0.9.30.so文件的hash值,发现不同,所以要修改ROP地址。

    由于libc文件太大,用手找太累了,所以使用了那篇文章中的ida的mipsrop插件,这里又踩了一个坑,因为我用的是ida7.0,而这个插件只能在ida6.8(更低的没试过)版本使用。

    修改了ROP后,再进行尝试exp,发现仍然失败,然后进行调试查看原因,跟踪ROP执行流,发现能成功跳转到栈上执行shellcode,但是shellcode和文章中的,文章中的shellcode开头有一个使用xor进行解密的过程,执行完之后的指令和文章中的不一样。所以准备自己写一个shellcode

    Day 4

    第四天就是开始写shellcod,首先给个mips指令和bin互转的网站:Online Assembler and Disassembler

    然后说说写的过程中遇到的问题,该路由器输入是不接受\x00\x20,所以ROP不是在ELF中寻找而是去libc中寻找:libuClibc基地址:0x2aae000httpd基地址:0x00400000

    如果在ELF中寻找ROP,则地址中总会有个\x00,所以ROP是在libc中寻找不存在\x00\x20的地址。但是在shellcode中,这两个字符却很难避免,所以那篇文章中对shellcode进行了xor加密

    wr940n的exp使用的是一个bind shell的shellcode,而我改成了一个反弹shell的shellcode

    然后就是最后遇到的一个大坑,使用gdb调试成功的一个反弹shell的shellcode,在实际测试中却失败了,使用gdb成功,直接打失败,因为这个问题折腾了挺长的时间

    然后查阅资料,在看雪的一篇文章中找到了原因:

    mips 的 exp 编写中还有一个问题就是 cache incoherency。MIPS CPUs 有两个独立的 cache:指令 cache 和数据 cache。指令和数据分别在两个不同的缓存中。当缓存满了,会触发 flush,将数据写回到主内存。攻击者的攻击 payload 通常会被应用当做数据来处理,存储在数据缓存中。当 payload 触发漏洞,劫持程序执行流程的时候,会去执行内存中的 shellcode。

    如果数据缓存没有触发 flush 的话,shellcode 依然存储在缓存中,而没有写入主内存。这会导致程序执行了本该存储 shellcode 的地址处随机的代码,导致不可预知的后果。

    最简单可靠的让缓存数据写入内存的方式是调用一个堵塞函数。比如 sleep(1) 或者其他类似的函数。sleep 的过程中,处理器会切换上下文让给其他正在执行的程序,缓存会自动执行 flush。

    这个坑点在那篇文章中也提及了,但是没具体说明,如果没实际踩一踩,不一定能理解。但是讲道理,如果直接用wr940n的exp,修改下ROP地址和shellcode,应该是不会遇到这个坑的,但是我仍然遇到了,经过研究发现,是usleep的问题,猜测是由于堵塞的时间过短所以未执行flush?然后进行实际测试了一番,把usleep的时间修改为18217,同样没用,然后简单看了下两者的汇编,发现usleep只是简单的调用nanosleep,而sleep除了调用nanosleep还进行其他相关的操作,网上没搜到相关文章,因为精力有限,作为遗留问题,以后有时间的时候再继续研究。

    不过有几个猜测,

    1. 时间问题,usleep的单位是微秒,18217也只有10ms,是不是要睡到1s?因为找不到合适的ROP,所以暂时没法证明
    2. flush内存是靠sleep中的几个信号相关的函数?

    所以最终我的做法是在wr940n的exp的ROP链中,调用的是usleep(0xc*2+1),但是我将usleep改成sleep => sleep(0xc*2+1),数据缓存被成功flush到主内存中,就能成功执行shellcode了

    Shellcode编写

    在本次研究中,最后时间的除了一开始的调试环境搭建外,就是shellcode的编写了,因为在那篇cve分析的文章中已经给出了wr940n的exp,ROP只需要修改修改地址就好了,所以工作量最大的还是在Shellcode的编写这一部分

    首先是syscall部分,比如:

    • mips采用的是RISC,32位系统下,指令固定采用4byte,syscall的字节码是\x0c,剩余的三字节默认用\x00补全,但是因为路由器不接受\x00的输入,所以在大端的情况下改成\x01\x01\x01\x0c,进行反汇编,就是syscall 0x40404

    系统调用的相关函数除了几个mips特有的,其他的都是跟linux下的syscall一样,可参考: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/mips/include/uapi/asm/unistd.h

    比如sys_socket

    所以$v0=4183表示的就是socket函数,具体参数信息可以去参考linux的系统调用: http://asm.sourceforge.net/syscall.html

    现在,先用c来实现一遍反连shell的代码:

    这里有个关键点,https://chromium.googlesource.com/chromiumos/third_party/glibc-ports/+/6cc02c7aaedec87cfb2d105f9682b12b2154e54f/sysdeps/unix/sysv/linux/mips/bits/socket.h

    和其他架构不一样,mips架构中,tcp是2,udp是1

    所以上面的代码比如在ubuntu中,是一个udp反连的代码,但是在mips中就是tcp反连

    还有一点就是wr941n是大端,所以12345端口是0x3039而不是0x3930,ip地址同理

    然后把上面代码转换成mips指令的汇编

    但是有个问题,之前说了该路由器不接收\x00\x20两个字符,而上面的汇编转换成字节码:

    所以要把这句指令进行修改, 因为$a0$a1的值都为2,所以可以这样修改:

    把上面的汇编转成shellcode替换exp中的shellcode,实际测试,又发现一个问题,设备成功反连了控制端,但是却不能执行命令,到路由器上用ps查看,发现sh已经变为僵尸进程

    经研究,问题出在execve("/bin/sh",0,0),如果我修改成execve("/bin/sh", ["/bin/sh", 0], 0)则成功反弹shell,可以任意命令执行

    参考链接

    1. https://www.fidusinfosec.com/tp-link-remote-code-execution-cve-2017-13772/
    2. https://github.com/rapid7/embedded-tools/tree/master/binaries/gdbserver
    3. http://shell-storm.org/online/Online-Assembler-and-Disassembler/?opcodes=%5Cx3c%5Cx1c%5Cx2a%5Cxb3%5Cx37%5Cx9c%5Cx17%5Cxb0&arch=mips32&endianness=big#disassembly
    4. https://www.kanxue.com/article-read-218.htm
    5. https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/mips/include/uapi/asm/unistd.h
    6. http://asm.sourceforge.net/syscall.html
    7. https://chromium.googlesource.com/chromiumos/third_party/glibc-ports/+/6cc02c7aaedec87cfb2d105f9682b12b2154e54f/sysdeps/unix/sysv/linux/mips/bits/socket.h
    作者:dawu | Categories:安全研究技术分享 | Tags:
  • D-Link系列路由器漏洞挖掘入门

    2017-10-27

    作者:Sebao@知道创宇404实验室

    前言

    前几天去上海参加了geekpwn,看着大神们一个个破解成功各种硬件,我只能在下面喊 6666,特别羡慕那些大神们。所以回来就决定好好研究一下路由器,争取跟上大神们的步伐。看网上公开的D-Link系列的漏洞也不少,那就从D-Link路由器漏洞开始学习。

    准备工作

    既然要挖路由器漏洞,首先要搞到路由器的固件。
    D-Link路由器固件下载地址:

    ftp://ftp2.dlink.com/PRODUCTS/

    下载完固件发现是个压缩包,解压之后里面还是有一个bin文件。听说用binwalk就可以解压。kali-linux自带binwalk,但是缺少一些依赖,所以还是编译安装了一下。

    按照上面的命令就可以完整的安装binwalk了,这样就可以解开市面上的大部分固件包。
    然后用 binwalk -Me 固件包名称 解固件,然后我们会得到以下划线开头的名称的文件夹,文件夹里squashfs-root文件夹,就是路由器的完整固件包。

    漏洞挖掘

    此文章针对历史路由器的web漏洞进行分析,路由器的web文件夹 一般就在suashfs-root/www或者
    suashfs-root/htdocs文件夹里。路由器固件所使用的语言一般为 asp,php,cgi,lua 等语言。这里主要进行php的代码审计来挖掘漏洞。

    Zoomeye dork: DIR-815 or DIR-645

    这里以 D-Link DIR-645固件为例,解开固件进入 suashfs-root/htdocs 文件夹。

    这个漏洞出现在 diagnostic.php文件。直接看代码

    分析代码可以看到,这里没有进行权限认证,所以可以直接绕过登录。继续往下看,set("/runtime/diagnostic/ping", $_POST["dst"]); 这段代码就是造成漏洞的关键代码。参数dst 没有任何过滤直接进入到了 ping的命令执行里,导致任意命令执行漏洞。继续往下看 $result = "OK";无论是否执行成功,这里都会显示OK。所以这是一个盲注的命令执行。以此构造payload

    因为是盲注的命令执行,所以这里需要借助一个盲打平台(如:ceye),来验证漏洞是否存在。

    Zoomeye dork:DIR-300 or DIR-600

    这里以 D-Link DIR-300固件为例,解开固件进入 suashfs-root/www 文件夹。

    漏洞出现在/model/__show_info.php文件。

    这里看到已经禁止了$REQUIRE_FILE的参数为var/etc/httpasswdvar/etc/hnapasswd。这么一看无法获取账号密码。但是我们可以从根路径开始配置httpasswd的路径,就可以绕过这个过滤了。

    payload:

    这里设置REQUIRE_FILE=/var/etc/httpasswd 成功绕过上面的 if判断,进行任意文件读取。

    Zoomeye dork:DIR-300 or DIR-615

    这里以 D-Link DIR-300固件为例,解开固件进入 suashfs-root/www 文件夹

    默认情况下,Web界面中的所有页面都需要进行身份验证,但是某些页面(如 登录页面) 必须在认证之前访问。 为了让这些页面不进行认证,他们设置了一个PHP变量NO_NEED_AUTH:

    此漏洞触发的原因在于 全局文件 _html_head.php

    这里我们看到 $NO_NEED_AUTH!="1" 如果 $NO_NEED_AUTH 不为 1 则进入身份认证。如果我们把$NO_NEED_AUTH值 设置为 1 那就绕过了认证进行任意操作。

    payload:

    localhost/bsc_lan.php?NO_NEED_AUTH=1&AUTH_GROUP=0

    这里AUTH_GROUP=0 表示admin权限

    Zoomeye dork:DIR-645

    这里以 D-Link DIR-300固件为例,解开固件进入 suashfs-root/htdocs 文件夹

    D-Link DIR-645 getcfg.php 文件由于过滤不严格导致信息泄露漏洞。

    这里我们可以看到 $GETCFG_SVC 没有任何过滤直接获取了 POST 传递过来的SERVICES的值。如果$GETCFG_SVC不为空,则进行文件读取。这里我们就可以读取存储此设备信息的DEVICE.ACCOUNT.xml.php文件。

    payload:

    总结

    可以发现此篇文章所提及的漏洞都是web领域的常见漏洞,如权限绕过,信息泄露,命令执行等漏洞。由于路由器的安全没有得到足够的重视,此文涉及到的漏洞都是因为对参数过滤不严格所导致的。
    路由器的漏洞影响还是很广泛的,在此提醒用户,及时更新路由器固件,以此避免各种入侵事件,以及个人信息的泄露。

    参考链接

    作者:dawu | Categories:安全研究技术分享 | Tags: