RSS Feed
更好更安全的互联网
  • 知道创宇404实验室2018年网络空间安全报告

    2019-01-17

    作者:知道创宇404实验室

    2018年是网络空间基础建设持续推进的一年,也是网络空间对抗激烈化的一年。IPV6的规模部署,让网络空间几何倍的扩大,带来的将会是攻击目标和攻击形态的转变。更多0day漏洞倾向于在曝光前和1day阶段实现价值最大化,也对防御方有了更高的要求。一手抓建设,一手抓防御,让2018年挑战与机遇并存。

    2018年知道创宇404实验室(以下简称404实验室)一共应急了135次,Seebug漏洞平台收录了664个漏洞,相比于2017年,应急的漏洞数量更多、涉及的设备范围更广。

    2018年上半年虚拟货币价值高涨所带来的是安全事件频发。区块链产业安全建设无法跟上虚拟货币的价值提升必然会导致安全事件的出现。由于区块链相关的攻击隐蔽且致命,监测、防御、止损等,都成为了区块链安全所需要面临的问题。“昊天塔(HaoTian)”是知道创宇404区块链安全研究团队独立开发的用于监控、扫描、分析、审计区块链智能合约安全自动化平台。《知道创宇以太坊合约审计CheckList》涵盖了超过29种会在以太坊审计过程中会遇到的问题,其中部分问题更是会影响到74.48%已公开源码的合约。

    2018年网络空间攻击正呈现出0day/1day漏洞的快速利用化、历史漏洞定期利用化的特点。勒索病毒和挖矿产业在2018年大行其道,僵尸网络在未来的网络空间对抗中也有可能被赋予新的使命。虚拟货币价值高涨让部分存在漏洞的高性能服务器成为挖矿产业的目标,IoT漏洞的不断涌现也让历史僵尸网络不断补充弹药库。

    2018年是数据泄漏事件频发曝光的一年。随着暗网用户的增多,黑市及虚拟货币的发展,暗网威胁必定会持续增长。知道创宇404安全研究团队也会持续通过技术手段来测绘暗网,提供威胁情报,追踪和对抗来自暗网的威胁。

    完整报告请参阅:《[知道创宇404实验室]2018年网络空间安全报告》


    Paper

    本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/788/

    作者:Elfinx | Categories:安全研究技术分享 | Tags:
  • 从 0 开始学 Linux 驱动开发(一)

    2019-01-08

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

    最近在搞IoT的时候,因为没有设备,模拟跑固件经常会缺/dev/xxx,所以我就开始想,我能不能自己写一个驱动,让固件能跑起来?因此,又给自己挖了一个很大坑,不管最后能不能达到我的初衷,能学到怎么开发Linux驱动,也算是有很大的收获了。

    前言

    我写的这个系列以实践为主,不怎么谈理论,理论可以自己去看书,我是通过《Linux Device Drivers》这本书学的驱动开发,Github上有这本书中讲解的实例的代码[1]

    虽然我不想谈太多理论,但是关于驱动的基本概念还是要有的。Linux系统分为内核态和用户态,只有在内核态才能访问到硬件设备,而驱动可以算是内核态中提供出的API,供用户态的代码访问到硬件设备。

    有了基本概念以后,我就产生了一系列的问题,而我就是通过我的这一系列的问题进行学习的驱动开发:

    1. 一切代码的学习都是从Hello World开始的,怎么写一个Hello World的程序?
    2. 驱动是如何在/dev下生成设备文件的?
    3. 驱动怎么访问实际的硬件?
    4. 因为我毕竟是搞安全的,我会在想,怎么获取系统驱动的代码?或者没有代码那能逆向驱动吗?驱动的二进制文件储存在哪?以后有机会可能还可以试试搞驱动安全。

    Everything start from Hello World

    提供我的Hello World代码[2]

    Linux下的驱动是使用C语言进行开发的,但是和我们平常写的C语言也有不同,因为我们平常写的C语言使用的是Libc库,但是驱动是跑在内核中的程序,内核中却不存在libc库,所以要使用内核中的库函数。

    比如printk可以类比为libc中的printf,这是在内核中定义的一个输出函数,但是我觉得更像Python里面logger函数,因为printk的输出结果是打印在内核的日志中,可以使用dmesg命令进行查看

    驱动代码只有一个入口点和一个出口点,把驱动加载到内核中,会执行module_init函数定义的函数,在上面代码中就是hello_init函数。当驱动从内核被卸载时,会调用module_exit函数定义的函数,在上面代码中就是hello_exit函数。

    上面的代码就很清晰了,当加载驱动时,输出Hello World,当卸载驱动时,输出Goodbye World

    PS:MODULE_LICENSEMODULE_AUTHOR这两个不是很重要,我又不是专业开发驱动的,所以不用关注这两个

    PSS: printk输出的结果要加一个换行,要不然不会刷新缓冲区

    编译驱动

    驱动需要通过make命令进行编译,Makefile如下所示:

    一般情况下,内核的源码都存在与/usr/src/linux-headers-$(shell uname -r)/目录下

    比如:

    而我们需要的是编译好后的源码的目录,也就是/usr/src/linux-headers-4.4.0-135-generic/

    驱动代码的头文件都需要从该目录下进行搜索

    M=$(PWD)该参数表示,驱动编译的结果输出在当前目录下

    最后通过命令obj-m := hello.o,表示把hello.o编译出hello.ko, 这个ko文件就是内核模块文件

    加载驱动到内核

    需要使用到的一些系统命令:

    • lsmod: 查看当前已经被加载的内核模块
    • insmod: 加载内核模块,需要root权限
    • rmmod: 移除模块

    比如:

    旧版的内核就是使用上面这样的方法进行内核的加载与移除,但是新版的Linux内核增加了对模块的验证,当前实际的情况如下:

    从安全的角度考虑,现在的内核都是假设模块为不可信的,需要使用可信的证书对模块进行签名,才能加载模块

    解决方法用两种:

    1. 进入BIOS,关闭UEFI的Secure Boot
    2. 向内核添加一个自签名证书,然后使用证书对驱动模块进行签名,参考[3]

    查看结果

    在/dev下增加设备文件

    同样先提供一份代码,然后讲解这份实例代码[4]

    知识点1 -- 驱动分类

    驱动分为3类,字符设备、块设备和网口接口,上面代码举例的是字符设备,其他两种,之后再说。

    如上图所示,brw-rw----权限栏,b开头的表示块设备(block),c开头的表示字符设备(char)

    知识点2 -- 主次编号

    主编号用来区分驱动,一般主编号相同的表示由同一个驱动程序控制。

    一个驱动中能创建多个设备,用次编号来区分。

    主编号和次编号一起,决定了一个驱动设备。

    如上图所示,

    设备sdasda1的主编号为8,一个此编号为0一个此编号为1

    知识点3 -- 驱动是如何提供API的

    在我的概念中,驱动提供的接口是/dev/xxx,在Linux下Everything is File,所以对驱动设备的操作其实就是对文件的操作,所以一个驱动就是用来定义,打开/读/写/......一个/dev/xxx将会发生啥,驱动提供的API也就是一系列的文件操作。

    有哪些文件操作?都被定义在内核<linux/fs.h>[5]头文件中,file_operations结构体

    上面我举例的代码中:

    我声明了一个该结构体,并赋值,除了owner,其他成员的值都为函数指针

    之后我在scull_setup_cdev函数中,使用cdev_add向每个驱动设备,注册该文件操作结构体

    比如我对该驱动设备执行open操作,则会去执行scull_open函数,相当于hook了系统调用中的open函数

    知识点4 -- 在/dev下生成相应的设备

    对上面的代码进行编译,得到scull.ko,然后对其进行签名,最后使用insmod加载进内核中

    查看是否成功加载:

    虽然驱动已经加载成功了,但是并不会在/dev目录下创建设备文件,需要我们手动使用mknod进行设备链接:

    总结

    在该实例中,并没有涉及到对实际物理设备的操作,只是简单的使用kmalloc在内核空间申请一块内存。代码细节上的就不做具体讲解了,都可以通过查头文件或者用Google搜出来。

    再这里分享一个我学习驱动开发的方法,首先看书把基础概念给弄懂,细节到需要用到的时候再去查。

    比如,我不需要知道驱动一共能提供有哪些API(也就是file_operations结构都有啥),我只要知道一个概念,驱动提供的API都是一些文件操作,而文件操作,目前我只需要open, close, read, write,其他的等有需求,要用到的时候再去查。

    参考

    1. https://github.com/jesstess/ldd4
    2. https://raw.githubusercontent.com/Hcamael/Linux_Driver_Study/master/hello.c
    3. https://jin-yang.github.io/post/kernel-modules.html
    4. https://raw.githubusercontent.com/Hcamael/Linux_Driver_Study/master/scull.c
    5. https://raw.githubusercontent.com/torvalds/linux/master/include/linux/fs.h

    Paper

    本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/779/

    作者:Elfinx | Categories:安全研究安全科普 | Tags:
  • Thinkphp5 远程代码执行漏洞事件分析报告

    2018-12-25
    作者:知道创宇404实验室
    时间:2018年12月19日

    0x00 背景

    2018年12月10日,ThinkPHP 官方发布《ThinkPHP 5.* 版本安全更新》,修复了一个远程代码执行漏洞。由于 ThinkPHP 框架对控制器名没有进行足够的检测,导致攻击者可能可以实现远程代码执行。

    知道创宇404实验室漏洞情报团队第一时间开始漏洞应急,复现了该漏洞,并进行深入分析。经过一系列测试和源码分析,最终确定漏洞影响版本为:

    • ThinkPHP 5.0.5-5.0.22
    • ThinkPHP 5.1.0-5.1.30

    在漏洞曝光后的第一时间,知道创宇404实验室积极防御团队积极排查知道创宇云安全的相关日志,发现该漏洞最早从 2018年9月开始,尚处于 0day 阶段时就已经被用于攻击多个虚拟货币类、金融类网站。

    在漏洞披露后的一周时间内,404实验室内部蜜罐项目也多次捕获到利用该漏洞进行攻击的案例,可以看到该漏洞曝光后短短8天就被僵尸网络整合到恶意样本中,并可以通过蠕虫的方式在互联网中传播。

    由于该漏洞触发方式简单、危害巨大,知道创宇404实验室在研究漏洞原理后,整理攻击事件,最终发布该漏洞事件报告。

    0x01 漏洞分析

    1.1 漏洞成因

    该漏洞出现的原因在于ThinkPHP5框架底层对控制器名过滤不严,从而让攻击者可以通过url调用到ThinkPHP框架内部的敏感函数,进而导致getshell漏洞,本文以ThinkPHP5.0.22为例进行分析。

    通过查看手册可以得知tp5支持多种路由定义方式:

    https://www.kancloud.cn/manual/thinkphp5/118037

    这里值得注意的地方有两个,一个是路由定义方式4,tp5可以将请求路由到指定类的指定方法(必须是public方法)中;另一个是即使没有定义路由,tp5默认会按照方式1对URL进行解析调度。

    然后来看一下具体的代码实现:

    thinkphp/library/think/App.php

    由于没有在配置文件定义任何路由,所以默认按照方式1解析调度。如果开启强制路由模式,会直接抛出错误。

    thinkphp/library/think/Route.php

    可以看到tp5在解析URL的时候只是将URL按分割符分割,并没有进行安全检测。继续往后跟:

    thinkphp/library/think/App.php

    在攻击时注意使用一个已存在的module,否则会抛出异常,无法继续运行。

    此处在获取控制器名时直接从之前的解析结果中获取,无任何安全检查。

    在这里对控制器类进行实例化,跟进去看一下:

    thinkphp/library/think/Loader.php

    根据传入的name获取对应的类,如果存在就直接返回这个类的一个实例化对象。

    跟进getModuleAndClass方法:

    可以看到如果控制器名中有\,就直接返回。

    回到thinkphp/library/think/App.phpmodule方法,正常情况下应该获取到对应控制器类的实例化对象,而我们现在得到了一个\think\App的实例化对象,进而通过url调用其任意的public方法,同时解析url中的额外参数,当作方法的参数传入。

    1.2 漏洞影响版本

    在与小伙伴做测试的时候,意外发现5.0.5版本使用现有的payload不生效,会报控制器不存在的错误。跟进代码之后发现了一些小问题,下面是ThinkPHP 5.0.5thinkphp/library/think/Loader.phpcontroller方法:

    以payload?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=id为例,我们将控制器名设置为\think\appstrpos返回了0,由于php弱类型问题,无法进入407行的判断,导致payload无效。这里可以将第一个\去掉来使payload生效,payload如下:

    继续查看ThinkPHP5.0.0-5.0.4的相关代码,发现5.0.0-5.0.4版本并没有对控制器名中有\的情况进行特殊处理,payload无法生效。

    以下是thinkphp 5.0.4thinkphp/library/think/Loader.php的相关代码:

    可以看到没有进行特殊处理,会统一进入parseClass进行统一处理。

    过滤掉了/ .,并且在最后会在前面拼接上控制器类的namespace,导致payload无法生效。从而最终确定ThinkPHP5.0受影响的版本为5.0.5-5.0.22

    1.3 漏洞防御

    1. 升级到Thinkphp最新版本:5.0.23、5.0.31
    2. 养成良好的开发习惯,使用强制路由模式,但不建议在线上环境直接开启该模式。
    3. 直接添加补丁,在thinkphp5.0版本的thinkphp/library/think/App.php554行,thinkphp5.1版本的thinkphp/library/think/route/dispatch/Url.php63行添加如下代码:

    0x02 实际攻击分析

    知道创宇404积极防御团队通过知道创宇旗下云防御产品“创宇盾”最早于2018年9月3日捕获该漏洞的payload,随后针对这个漏洞的攻击情况做了详细的监控及跟进:

    2.1 0day在野

    在官方发布更新前,在知道创宇云安全的日志中共检测到62次漏洞利用请求,以下是对部分攻击事件的分析。

    2018年9月3日,ip 58.49.*.*(湖北武汉)对某网站发起攻击,使用的payload如下:

    这是一个日后被广泛利用的payload,通过调用file_put_contents将php代码写入文件来验证漏洞是否存在。

    2018年10月16日,该ip又对另一网站进行攻击,此次攻击使用的payload如下:

    此payload针对Thinkphp 5.1.x,直接调用phpinfo,简化了漏洞验证流程。值得一提的是,该ip是日志中唯一一个在不同日期发起攻击的ip。

    2018年10月6日,ip 172.111.*.*(奥地利)对多个虚拟币类网站发起攻击,payload均是调用file_put_contents写入文件以验证漏洞是否存在:

    2018年12月9日,ip 45.32.*.*(美国)对多个投资金融类网站发起攻击,payload都是调用phpinfo来进行漏洞验证:

    2.2 0day曝光后

    在官方发布安全更新后,知道创宇404实验室成功复现了漏洞,并更新了WAF防护策略。与此同时,攻击数量激增,漏洞被广泛利用。在官方发布安全更新的8天时间里(2018/12/09 - 2018/12/17),共有5570个IP对486962个网站发起2566078次攻击。

    与此同时,404实验室内部蜜罐项目从漏洞披露后三天(12月13日)开始,捕获到对该漏洞的探测,在如下几个目录进行探测:

    使用的探测拼接参数为:

    12月18日,已有僵尸网络将该漏洞exp整合到恶意样本中,在互联网上传播。捕获的攻击流量为:

    经过简单分析,该样本使用 CVE-2017-17215 、CNVD-2014-01260 和 ThinkPHP5 远程代码执行漏洞进行传播。

    0x03 小结

    此漏洞是继ECShop代码执行漏洞之后,又一次经典的0day漏洞挖掘利用过程。从漏洞刚被挖掘出来时的试探性攻击,到之后有目的、有针对性地攻击虚拟币类、投资金融类的网站,最后到漏洞曝光后的大规模批量性攻击,成为黑产和僵尸网络的工具,给我们展示了一条完整的0day漏洞生命线。由于ThinkPHP是一个开发框架,有大量cms、私人网站在其基础上进行开发,所以该漏洞的影响可能比我们看到的更加深远。


    Paper 本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/770/

    作者:Elfinx | Categories:安全研究漏洞通告 | Tags:
  • lucky 勒索病毒分析与文件解密

    2018-12-17
    作者:Hcamael & 0x7F@知道创宇404实验室
    时间:2018年12月4日

    0x00 前言

    近日,互联网上爆发了一种名为 lucky 的勒索病毒,该病毒会将指定文件加密并修改后缀名为 .lucky

    知道创宇 404 实验室的炼妖壶蜜罐系统最早于 2018.11.10 就捕捉到该勒索病毒的相关流量,截止到 2018.12.04 日,该病毒的 CNC 服务器依然存活。

    根据分析的结果可以得知 lucky 勒索病毒几乎就是 Satan 勒索病毒,整体结构并没有太大改变,包括 CNC 服务器也没有更改。Satan 病毒一度变迁:最开始的勒索获利的方式变为挖矿获利的方式,而新版本的 lucky 勒索病毒结合了勒索和挖矿。

    知道创宇 404 实验室在了解该勒索病毒的相关细节后,迅速跟进并分析了该勒索病毒;着重分析了该病毒的加密模块,并意外发现可以利用伪随机数的特性,还原加密密钥,并成功解密了文件,Python 的解密脚本链接: https://github.com/knownsec/Decrypt-ransomware

    本文对 lucky 勒索病毒进行了概要分析,并着重分析了加密流程以及还原密钥的过程。

    0x01 lucky 病毒简介

    lucky 勒索病毒可在 Windows 和 Linux 平台上传播执行,主要功能分为「文件加密」、「传播感染」与「挖矿」。

    文件加密
    lucky 勒索病毒遍历文件夹,对如下后缀名的文件进行加密,并修改后缀名为 .lucky

    为了保证系统能够正常的运行,该病毒加密时会略过了系统关键目录,如:

    传播感染
    lucky 勒索病毒的传播模块并没有做出新的特色,仍使用了以下的漏洞进行传播:

    挖矿
    该勒索病毒采用自建矿池地址:194.88.105.5:443,想继续通过挖矿获得额外的收益。同时,该矿池地址也是 Satan 勒索病毒变种使用的矿池地址。

    运行截图

    0x02 病毒流程图

    lucky 勒索病毒的整体结构依然延续 Satan 勒索病毒的结构,包括以下组件:

    流程图大致如下:

    lucky 勒索病毒的每个模块都使用了常见的壳进行加壳保护,比如 UPXMPRESS,使用常见的脱壳软件进行自动脱壳即可。

    0x03 加密流程

    对于一个勒索病毒来说,最重要的就是其加密模块。在 lucky 勒索病毒中,加密模块是一个单独的可执行文件,下面对加密模块进行详细的分析。(以 Windows 下的 cpt.exe 作为分析样例)

    1.脱去upx
    cpt.exe 使用 upx 进行加壳,使用常见的脱壳工具即可完成脱壳。

    2.加密主函数
    使用 IDA 加载脱壳后的 cpt.exe.unp,在主函数中有大量初始化的操作,忽略这些操作,跟入函数可以找到加密逻辑的主函数,下面对这些函数进行标注:

    generate_key: 生成 60 位随机字符串,用于后续加密文件。
    wait_sleep: 等待一段时间。
    generate_session: 生成 16 位随机字符串,作为用户的标志(session)。
    lucky_crypto_entry: 具体加密文件的函数。
    send_info_to_server: 向服务器报告加密完成。

    大致的加密流程就是函数标注的如此,最后写入一个文件 c:\\_How_To_Decrypt_My_File_.Dic,通知用户遭到了勒索软件加密,并留下了比特币地址。

    3.generate_key()
    该函数是加密密钥生成函数,利用随机数从预设的字符串序列中随机选出字符,组成一个长度为 60 字节的密钥。

    byte_56F840 为预设的字符串序列,其值为:

    4.generate_session()
    加密模块中使用该函数为每个用户生成一个标识,用于区分用户;其仍然使用随机数从预设的字符串序列中随机选出字符,最后组成一个长度为 16 字节的 session,并存入到 C:\\Windows\\Temp\\Ssession 文件下。

    其中 byte_56F800 字符串为:

    5.lucky_crypto_entry()

    文件名格式

    该函数为加密文件的函数入口,提前拼接加密文件的文件名格式,如下:

    被加密的文件的文件名格式如下:

    其中 filename 是文件本身的名字,后续的字符串是用户的 session。

    通知服务器

    在加密前,还会首先向服务器发送 HTTP 消息,通知服务器该用户开始执行加密了:

    HTTP 数据包格式如下:

    文件筛选

    在加密模块中,lucky 对指定后缀名的文件进行加密:

    被加密的后缀名文件包括:

    6.AES_ECB 加密方法
    lucky 使用先前生成的长度为 60 字节的密钥,取前 32 字节作为加密使用,依次读取文件,按照每 16 字节进行 AEC_ECB 加密。

    除此之外,该勒索病毒对于不同文件大小有不同的处理,结合加密函数的上下文可以得知,这里我们假设文件字节数为 n:

    1. 对于文件末尾小于 16 字节的部分,不加密
    2. 若 n > 10000000 字节,且当 n > 99999999 字节时,将文件分为 n / 80 个块,加密前 n / 16 个块
    3. 若 n > 10000000 字节,且当 99999999 <= n <= 499999999 字节时,将文件分为 n / 480 个块,加密前 n / 16 个块
    4. 若 n > 10000000 字节,且当 n > 499999999 字节时,将文件分为 n / 1280 个块,加密前 n / 16 个块

    对于每个文件在加密完成后,lucky 病毒会将用于文件加密的 AES 密钥使用 RSA 算法打包并添加至文件末尾。

    7.加密完成
    在所有文件加密完成后,lucky 再次向服务器发送消息,表示用户已经加密完成;并在 c:\\_How_To_Decrypt_My_File_.Dic,通知用户遭到了勒索软件加密。

    加密前后文件对比:

    0x04 密钥还原

    在讨论密钥还原前,先来看看勒索病毒支付后流程。

    如果作为一个受害者,想要解密文件,只有向攻击者支付 1BTC,并把被 RSA 算法打包后的 AES 密钥提交给攻击者,攻击者通过私钥解密,最终返回明文的 AES 密钥用于文件解密;可惜的是,受害者即便拿到密钥也不能立即解密,lucky 勒索病毒中并没有提供解密模块。

    勒索病毒期待的解密流程:

    那么,如果能直接找到 AES 密钥呢?

    在完整的分析加密过程后,有些的小伙伴可能已经发现了细节。AES 密钥通过 generate_key() 函数生成,再来回顾一下该函数:

    利用当前时间戳作为随机数种子,使用随机数从预设的字符串序列中选取字符,组成一个长度为 60 字节的密钥。

    随机数=>伪随机数
    有过计算机基础的小伙伴,应该都知道计算机中不存在真随机数,所有的随机数都是伪随机数,而伪随机数的特征是「对于一种算法,若使用的初值(种子)不变,那么伪随机数的数序也不变」。所以,如果能够确定 generate_key() 函数运行时的时间戳,那么就能利用该时间戳作为随机种子,复现密钥的生成过程,从而获得密钥。

    确定时间戳

    爆破

    当然,最暴力的方式就是直接爆破,以秒为单位,以某个有标志的文件(如 PDF 文件头)为参照,不断的猜测可能的密钥,如果解密后的文件头包含 %PDF(PDF 文件头),那么表示密钥正确。

    文件修改时间

    还有其他的方式吗?文件被加密后会重新写入文件,所以从操作系统的角度来看,被加密的文件具有一个精确的修改时间,可以利用该时间以确定密钥的生成时间戳:

    如果需要加密的文件较多,加密所花的时间较长,那么被加密文件的修改时间就不是生成密钥的时间,应该往前推移,不过这样也大大减少了猜测的范围。

    利用用户 session

    利用文件修改时间大大减少了猜测的范围;在实际测试中发现,加密文件的过程耗时非常长,导致文件修改时间和密钥生成时间相差太多,而每次都需要进行检查密钥是否正确,需要耗费大量的时间,这里还可以使用用户 session 进一步缩小猜测的范围。

    回顾加密过程,可以发现加密过程中,使用时间随机数生成了用户 session,这就成为了一个利用点。利用时间戳产生随机数,并使用随机数生成可能的用户 session,当找到某个 session 和当前被加密的用户 session 相同时,表示该时刻调用了 generate_session() 函数,该函数的调用早于文件加密,晚于密钥生成函数。

    找到生成用户session 的时间戳后,再以该时间为起点,往前推移,便可以找到生成密钥的时间戳。

    补充:实际上是将整个还原密钥的过程,转换为寻找时间戳的过程;确定时间戳是否正确,尽量使用具有标志的文件,如以 PDF 文件头 %PDF 作为明文对比。

    还原密钥
    通过上述的方式找到时间戳,利用时间戳就可以还原密钥了,伪代码如下:

    文件解密
    拿到了 AES 密钥,通过 AES_ECB 算法进行解密文件即可。

    其中注意两点:

    0x05 总结

    勒索病毒依然在肆掠,用户应该对此保持警惕,虽然 lucky 勒索病毒在加密环节出现了漏洞,但仍然应该避免这种情况;针对 lucky 勒索病毒利用多个应用程序的漏洞进行传播的特性,各运维人员应该及时对应用程序打上补丁。

    除此之外,知道创宇 404 实验室已经将文中提到的文件解密方法转换为了工具,若您在此次事件中,不幸受到 lucky 勒索病毒的影响,可以随时联系我们。


    References:
    tencent: https://s.tencent.com/research/report/571.html
    绿盟: https://mp.weixin.qq.com/s/uwWTS_ta29YlYntaZN3omQ
    深信服: https://mp.weixin.qq.com/s/zA1bK1sLwaZsUvuOzVHBKg
    Python 的解密脚本: https://github.com/knownsec/Decrypt-ransomware


    Paper 本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/758/

    作者:Elfinx | Categories:安全研究技术分享 | Tags:
  • Discuz x3.4 前台 SSRF 分析

    2018-12-10
    作者:LoRexxar'@知道创宇404实验室
    时间:2018年12月7日
    2018年12月3日,@L3mOn公开了一个Discuz x3.4版本的前台SSRF,通过利用一个二次跳转加上两个解析问题,可以巧妙地完成SSRF攻击链。https://www.cnblogs.com/iamstudy/articles/discuz_x34_ssrf_1.html

    在和小伙伴@Dawu复现过程中发现漏洞本身依赖多个特性,导致可利用环境各种缩减和部分条件的强依赖关系大大减小了该漏洞的危害。后面我们就来详细分析下这个漏洞。

    漏洞产生条件

    • 版本小于 41eb5bb0a3a716f84b0ce4e4feb41e6f25a980a3 补丁链接
    • PHP版本大于PHP 5.3
    • php-curl <= 7.54
    • DZ运行在80端口
    • 默认不影响linux(未100%证实,测试常见linux环境为不影响)

    漏洞复现

    ssrf

    首先漏洞点出现的位置在/source/module/misc/misc_imgcropper.php line 55

    这里$prefix变量为/然后后面可控,然后进入函数里

    /source/class/class_image.php line 52 Thumb函数

    然后跟入init函数(line 118)中

    很明显只要parse_url解得出host就可以通过dfsockopen发起请求

    由于这里前面会补一个/,所以这里的source必须是/开头,一个比较常见的技巧。

    这样的链接会自动补上协议,至于怎么补就要看具体的客户端怎么写的了。

    我们接着跟dfsockopen/source/function/function_core.php line 199

    然后到source/function/function_filesock.php line 31

    主要为红框部分的代码,可以看到请求的地址为parse_url下相应的目标。

    由于前面提到,链接的最前为/,所以这里的parse_url就受到了限制。

    由于没有scheme,所以最终curl访问的链接为

    前面自动补协议就成了

    这里就涉及到了很严重的问题,就是对于curl来说,请求一个空host究竟会请求到哪里呢?

    在windows环境下,libcurl版本7.53.0

    可以看到这里请求了我本机的ipv6的ip。

    在linux环境(ubuntu)下,截图为7.47.0

    测试了常见的各种系统,测试过程中没有找到不会报错的curl版本,暂时认为只影响windows的服务端环境。

    再回到代码条件下,可以把前面的条件回顾一下:

    1、首先我们需要保证/{}可控在解parse_url操作下存在host。

    要满足这个条件,我们首先要对parse_url的结果有个清晰的认识。

    在没有协议的情况下,好像是参数中不能出现协议或者端口(:号),否则就不会把第一段解析成host,虽然还不知道为什么,这里暂且不论。

    在这种情况下,我们只需要把后面可能出现的http去掉就好了,因为无协议的情况下会默认补充http在前面(一般来说)。

    2、curl必须要能把空host解析成localhost,所以libcurl版本要求在7.54.0以下,而且目前测试只影响windows服务器(欢迎打脸

    3、dz必须在80端口下

    在满足上面的所有条件后,我们实际请求了本地的任意目录

    但这实际上来说没有什么用,所以我们还需要一个任意url跳转才行,否则只能攻击本地意义就很小了。

    任意url跳转

    为了能够和前面的要求产生联动,我们需要一个get型、不需要登陆的任意url跳转。

    dz在logout的时候会从referer参数(非header头参数)中获取值,然后进入301跳转,而这里唯一的要求是对host有一定的验证,让我们来看看代码。

    /source/function/function_core.php:1498

    上面的截图解释了这段代码的主要问题,核心代码为红框部分。

    为了让referer不改变,我们必须让host只有一个字符,但很显然,如果host只能有一个字符,我们没办法控制任意url跳转。

    所以我们需要想办法让parse_urlcurl对同一个url的目标解析不一致,才有可能达到我们的目标。

    上面这个链接parse_url解析出来为localhost,而curl解释为www.baidu.com

    我们抓个包来看看

    成功绕过了各种限制

    利用

    到现在我们手握ssrf+任意url跳转,我们只需要攻击链连接起来就可以了。攻击流程如下

    当然最开始访问cutimg页面时,需要先获取formhash,而且referer也要相应修改,否则会直接拦截。

    exp演示


    Paper 本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/756/

    作者:Elfinx | Categories:安全研究 | Tags:
  • Code Breaking 挑战赛 Writeup

    2018-12-10
    作者:LoRexxar'@知道创宇404实验室
    时间:2018年12月7日
    @phith0n 在代码审计小密圈二周年的时候发起了Code-Breaking Puzzles挑战赛,其中包含了php、java、js、python各种硬核的代码审计技巧。在研究复现the js的过程中,我花费了大量的精力,也逐渐找到代码审计的一些技巧,这里主要分享了5道ez题目和1道hard的the js这道题目的writeup,希望阅读本文的你可以从题目中学习到属于代码审计的思考逻辑和技巧。

    easy - function

    思路还算是比较清晰,正则很明显,就是要想办法在函数名的头或者尾找一个字符,不影响函数调用。

    简单实验了一下没找到,那就直接fuzz起来吧

    很容易就fuzz到了就是\这个符号

    后来稍微翻了翻别人的writeup,才知道原因,在PHP的命名空间默认为\,所有的函数和类都在\这个命名空间中,如果直接写函数名function_name()调用,调用的时候其实相当于写了一个相对路径;而如果写\function_name() 这样调用函数,则其实是写了一个绝对路径。如果你在其他namespace里调用系统类,就必须写绝对路径这种写法。

    紧接着就到了如何只控制第二个参数来执行命令的问题了,后来找到可以用create_function来完成,create_function的第一个参数是参数,第二个参数是内容。

    函数结构形似

    然后执行,如果我们想要执行任意代码,就首先需要跳出这个函数定义。

    这样一来,我们想要执行的代码就会执行

    exp

    easy pcrewaf

    这题自己研究的时候没想到怎么做,不过思路很清楚,文件名不可控,唯一能控制的就是文件内容。

    所以问题的症结就在于如何绕过这个正则表达式。

    简单来说就是<后面不能有问号,<?后面不能有(;?>反引号,但很显然,这是不可能的,最少执行函数也需要括号才行。从常规的思路肯定不行

    https://www.leavesongs.com/PENETRATION/use-pcre-backtrack-limit-to-bypass-restrict.html

    之后看ph师傅的文章我们看到了问题所在,pcre.backtrack_limit这个配置决定了在php中,正则引擎回溯的层数。而这个值默认是1000000.

    而什么是正则引擎回溯呢?

    在正则中.*表示匹配任意字符任意位,也就是说他会匹配所有的字符,而正则引擎在解析正则的时候必然是逐位匹配的,对于

    这段代码来说

    但很显然,服务端不可能不做任何限制,不然如果post一个无限长的数据,那么服务端就会浪费太多的资源在这里,所以就有了pcre.backtrack_limit,如果回溯次数超过100万次,那么匹配就会结束,然后跳过这句语句。

    回到题目来看,如果能够跳过这句语句,我们就能上传任意文件内容了!

    所以最终post就是传一个内容为

    对于任何一种引擎来说都涉及到这个问题,尤其对于文件内容来说,没办法控制文件的长度,也就不可避免的会出现这样的问题。

    对于PHP来说,有这样一个解决办法,在php的正则文档中提到这样一个问题

    preg_match返回的是匹配到的次数,如果匹配不到会返回0,如果报错就会返回false

    所以,对preg_match来说,只要对返回结果有判断,就可以避免这样的问题。

    easy - phpmagic

    题目代码简化之后如下

    稍微阅读一下代码不难发现问题有几个核心点

    1、没办法完全控制dig的返回,由于没办法命令注入,所以这里只能执行dig命令,唯一能控制的就是dig的目标,而且返回在显示之前还转义了尖括号,所以

    2、in_array(pathinfo($log_name, PATHINFO_EXTENSION), ['php', 'php3', 'php4', 'php5', 'phtml', 'pht'], true)这句过滤真的很严格,实在的讲没有什么直白的绕过办法。

    3、log前面会加上$_SERVER['SERVER_NAME']

    第一点真的是想不到,是看了别人的wp才想明白这个关键点 http://f1sh.site/2018/11/25/code-breaking-puzzles%E5%81%9A%E9%A2%98%E8%AE%B0%E5%BD%95/

    之前做题的时候曾经遇到过类似的问题,可以通过解base64来隐藏自己要写入的内容绕过过滤,然后php在解析的时候会忽略各种乱码,只会从<?php开始,所以其他的乱码都不会影响到内容,唯一要注意的就是base64是4位一解的,主要不要把第一位打乱掉。

    简单测试一下

    这样一来我们就能控制文件内容了,而且可以注入<?php

    接下来就是第二步,怎么才能控制logname为调用php伪协议呢?

    问题就在于我们如何控制$_SERVER['SERVER_NAME'],而这个值怎么定是不一定的,这里在这个题目中是取自了头中的host。

    这样一来头我们可以控制了,我们就能调用php伪协议了,那么怎么绕过后缀限制呢?

    这里用了之前曾经遇到过的一个技巧(老了记性不好,翻了半天也没找到是啥比赛遇到的),test.php/.就会直接调用到test.php

    通过这个办法可以绕过根据.来分割后缀的各种限制条件,同样也适用于当前环境下。

    最终poc:

    easy - phplimit

    这个代码就简单多了,简单来说就是只能执行一个函数,但不能设置参数,这题最早出现是在RCTF2018中

    https://lorexxar.cn/2018/05/23/rctf2018/

    在原来的题目中是用next(getallheaders())绕过这个限制的。

    但这里getallheaders是apache中的函数,这里是nginx环境,所以目标就是找一个函数其返回的内容是可以控制的就可以了。

    问题就在于这种函数还不太好找,首先nginx中并没有能获取all header的函数。

    所以目标基本就锁定在会不会有获取cookie,或者所有变量这种函数。在看别人writeup的时候知道了get_defined_vars这个函数

    http://php.net/manual/zh/function.get-defined-vars.php

    他会打印所有已定义的变量(包括全局变量GET等)。简单翻了翻PHP的文档也没找到其他会涉及到可控变量的

    在原wp中有一个很厉害的操作,直接reset所有的变量。

    http://f1sh.site/2018/11/25/code-breaking-puzzles%E5%81%9A%E9%A2%98%E8%AE%B0%E5%BD%95/

    然后只有当前get赋值,那么就只剩下get请求的变量了

    后面就简单了拼接就好了

    然后...直接列目录好像也是个不错的办法2333

    easy - nodechr

    nodejs的一个小问题,关键代码如下

    这里的注入应该是比较清楚的,直接拼接进查询语句没什么可说的。

    然后safekeyword过滤了select union -- ;这四个,下面的逻辑其实说简单的就一句

    如何构造这句来查询flag,开始看到题一味着去想盲注的办法了,后来想明白一点,在注入里,没有select是不可能去别的表里拿数据的,而题目一开始很明确的表明flag在flag表中。

    所以问题就又回到了最初的地方,如何绕过safekeyword的限制。

    ph师傅曾经写过一篇文章 https://www.leavesongs.com/HTML/javascript-up-low-ercase-tip.html

    在js中部分字符会在toLowerCase和toUpperCase处理的时候发生难以想象的变化

    用在这里刚好合适不过了。

    hard - thejs

    javascript真难....

    关键代码以及注释如下

    由于对node不熟,初看代码的时候简单研究了一下各个部分都是干嘛的。然后就发现整个站几乎没什么功能,就是获取输入然后取其中固定的输出,起码就自己写的代码来说不可能有问题。

    再三思考下觉得可能问题在引入的包中...比较明显的就是lodash.merge这句,这句代码在这里非常刻意,于是就顺着这个思路去想,简单翻了一下代码发现没什么收获。后来@spine给了我一个链接

    https://github.com/HoLyVieR/prototype-pollution-nsec18/blob/master/paper/JavaScript_prototype_pollution_attack_in_NodeJS.pdf

    js特性

    首先我们可以先回顾一下js的一部分特性。

    由于js非常面向对象的编程特性,js有很多神奇的操作。

    在js中你可以用各种方式操作自己的对象。

    在js中,所有的对象都是从各种基础对象继承下来的,所以每个对象都有他的父类,通过prototype可以直接操作修改父类的对象。

    而且子类会继承父类的所有方法

    在js中,每个对象都有两个魔术方法,一个是constructor另一个是__proto__

    对于实例来说,constructor代表其构造函数,像前面说的一样,函数可以通过prototype获取其父对象

    而另一个魔术方法__proto__就等价于.constructor.prototype

    由于子类会继承父类的所有方法,所以如果在当前对象中找不到该方法,就会到父类中去找,直到找不到才会爆错

    在复习了上面的特性之后,我们回到这个漏洞

    回到漏洞

    在漏洞分析文中提到了这样一种方式

    假设对于语句

    如果我们控制a为constructor,b为prototype,c为某个key,我们是不是就可以为这个对象父类初始化某个值,这个值会被继承到当前对象。同理如果a为__proto__,b也为__proto__,那么我们就可以为基类Object定义某个值。

    当然这种代码不会随时都出现,所以在实际场景下,这种攻击方式会影响什么样的操作呢。

    首先我们需要理解的就是,我们想办法赋值的__proto__对象并不是真正的这个对象,如图

    所以想要写到真正的__proto__中,我们需要一层赋值,就如同原文范例代码中的那样

    通过这样的操作,我们就可以给Object基类定义一个变量名。

    由于子类会继承父类的所有方法,但首先需要保证子类没有定义这个变量,因为只有当前类没有定义这个变量,才会去父类寻找

    在js代码中,经常能遇到这样的代码

    这种情况下,js会去调用obj的aaa方法,如果aaa方法undefined,那么就会跟入到obj的父类中(js不会直接报该变量未定义并终止)。

    这种情况下,我们通过定义obj的基类Object的aaa方法,就能操作这个变量,改变原来的代码走向。

    最后让我们回到题目中来。

    回到题目

    回到题目中,这下代码的问题点很清楚了。整个代码有且只有1个输入点也就是req.body,这个变量刚好通过lodash.merge合并.

    这里的lodash.merge刚好也就是用于将两个对象合并,成功定义了__proto__对象的变量。

    我们也可以通过上面的技巧去覆盖某个值,但问题来了,我们怎么才能getshell呢?

    顺着这个思路,我需要在整个代码中寻找一个,在影响Object之后,且可以执行命令的地方。

    很幸运的是,虽然我没有特别研究明白nodejs,但我还是发现模板是动态生成的。

    这里的代码是在请求后完成的(动态渲染?)

    跟入到template函数中,可以很清楚的看到

    接下来就是这一大串代码中寻找一个可以影响的变量,我们的目标是找一个未定义的变量,且后面有判断调用它

    这里的sourceURL刚好符合这个条件,我们直接跟入前面的options定义处,进入函数一直跟下去,直到lodash.js的3515行。

    可以看到object本身没有这个方法,但仍然遍历到了,成功注入了这个变量,紧接着渲染模板就成功执行代码了。

    完成攻击

    其实发现可以注入代码之后就简单了,我朋友说他不能用child_process来执行命令,我测试了一下发现是可以的,只是不能弹shell回来不知道怎么回事。思考了一下决定直接wget外带数据出来吧。

    poc

    需要注意一定要是json格式,否则__proto__会解成字符串,开始坑了很久。

    直接偷懒用ceye接请求,其实用什么都行


    Paper 本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/755/

    作者:Elfinx | Categories:安全研究技术分享 | Tags:
  • LCTF2018 ggbank 薅羊毛实战

    2018-11-21
    作者:LoRexxar'@知道创宇404区块链安全研究团队
    时间:2018年11月20日
    11.18号结束的LCTF2018中有一个很有趣的智能合约题目叫做ggbank,题目的原意是考察弱随机数问题,但在题目的设定上挺有意思的,加入了一个对地址的验证,导致弱随机的难度高了许多,反倒是薅羊毛更快乐了,下面就借这个题聊聊关于薅羊毛的实战操作。

    分析

    源代码
    https://ropsten.etherscan.io/address/0x7caa18d765e5b4c3bf0831137923841fe3e7258a#code

    首先我们照例来分析一下源代码

    和之前我出的题风格一致,首先是发行了一种token,然后基于token的挑战代码,主要有几个点

    跟着看checkfriend函数

    checkfriend就是整个挑战最大的难点,也大幅度影响了思考的方向,这个稍后再谈。

    空投函数没看有什么太可说的,就是对每一个新用户都发一次空投。

    然后就是goodluck函数

    然后只要余额大于200000就可以拿到flag。

    其实代码特别简单,漏洞也不难,就是非常常见的弱随机数问题。

    随机数的生成方式为

    另一个的生成方式为

    其实非常简单,这两个数字都是已知的,msg.sender可以直接控制已知的地址,那么左值就是已知的,剩下的就是要等待一个右值出现,由于block.number是自增的,我们可以通过提前计算出一个block.number,然后写脚本监控这个值出现,提前开始发起交易抢打包,就ok了。具体我就不详细提了。可以看看出题人的wp。

    https://github.com/LCTF/LCTF2018/tree/master/Writeup/gg%20bank

    但问题就在于,这种操作要等block.number出现,而且还要抢打包,毕竟还是不稳定的。所以在做题的时候我们关注到另一条路,薅羊毛,这里重点说说这个。

    合约薅羊毛

    在想到原来的思路过于复杂之后,我就顺理成章的想到薅羊毛这条路,然后第一反正就是直接通过合约建合约的方式来碰这个概率。

    思路来自于最早发现的薅羊毛合约https://paper.seebug.org/646/

    这个合约有几个很精巧的点。

    首先我们需要有基本的概念,在以太坊上发起交易是需要支付gas的,如果我们不通过合约来交易,那么这笔gas就必须先转账过去eth,然后再发起交易,整个过程困难了好几倍不止。

    然后就有了新的问题,在合约中新建合约在EVM中,是属于高消费的操作之一,在以太坊中,每一次交易都会打包进一个区块中,而每一个区块都有gas消费的上限,如果超过了上限,就会爆gas out,然后交易回滚,交易就失败了。

    上述的poc中,有一个很特别的点就是我加入了checkfriend的判断,因为我发现循环中如果新建合约的函数调用revert会导致整个交易报错,所以我干脆把整个判断放上来,在判断后再发起交易。

    可问题来了,我尝试跑了几波之后发现完全不行,我忽略了一个问题。

    让我们回到checkfriend

    checkfriend只接受地址中带有7d7ec的地址交易,光是这几个字母出现的概率就只有1/36*1/36*1/36*1/36*1/36这个几率在每次随机生成50个合约上计算的话,概率就太小了。

    必须要找新的办法来解决才行。

    python脚本解决方案

    既然在合约上没办法,那么我直接换用python写脚本来解决。

    这个挑战最大的问题就在于checkfriend这里,那么我们直接换一种思路,如果我们去爆破私钥去恢复地址,是不是更有效一点儿?

    其实爆破的方式非常多,但有的恢复特别慢,也不知道瓶颈在哪,在换了几种方式之后呢,我终于找到了一个特别快的恢复方式。

    我们拿到了地址之后就简单了,首先先转0.01eth给它,然后用私钥发起交易,获得空投、转账回来。

    需要注意的是,转账之后需要先等到转账这个交易打包成功,之后才能继续下一步交易,需要多设置一步等待。

    有个更快的方案是,先跑出200个地址,然后再批量转账,最后直接跑起来,不过想了一下感觉其实差不太多,因为整个脚本跑下来也就不到半小时,速度还是很可观的。

    脚本如下