MySQL Order By 注入的小技巧
0x00 前言 前几年在工作上遇到了一个 MySQL 的注入问题,注入点在 order by 后面,类似于 select 1 from dual order by "", *;` 其中, * 所在的地方即为注入点。 随即本人尝试对这个点进行注入,经过若干次尝试后均以失败告终。 具体尝试结果如下: 1. 报错注入 尝试使用常见的 GTID_SUBSET() 函数进行报错注入,发现 order by 后没有发挥应用的效果。 2. 延时注入 尝试使用的 sleep() 函数进行延时注入,发现 order by 后没有发挥应用的效果。 此时你可能会发现, order by 后面的行为稍显怪异,难以琢磨。 难道就没有办法利用这个注入了吗?肯定得有,不然这篇文章就写不下去了。 0x01 order by 后的注入 POC 在经过若干次尝试后,本人发现下面的查询语句能够触发报错。 select 1 from dual order by "",(SELECT(1)FROM(SELECT(GTID_SUBSET(2,2))where(1=1))test); # 报错 select 1 from dual order by "",(SELECT(1)FROM(SELECT(GTID_SUBSET(2,2))where(1=2))test); # 不报错 因此,完全可以利用上述语句进行 bool 类型的注入, 甚至直接一点,利用该特性进行报错注入: 或者将 GTID_SUBSET 换成 sleep 进行延时注入。 0x02 未完待续 本文能够利用一些特殊的 SQL,对 order by 后的注入点进行注入。然而,这个只是表现,并非原理。至于 MySQL 的行为为何如此奇怪,本人并没有了解清楚,有待研究。 ...
再谈 IP 伪造
0x00 前言 本篇文章最早由笔者发布在漕河泾小黑屋公众号上。然而,公众号始终不利于分享,故重新发表于博客上。 本篇文章主要介绍几种常见的伪造来源 IP 的方式。 0x01 方式1: X-Forwarded-For 这个是最为认知的 IP 伪造方法,早年的 CTF 题目也经常涉及,然而现在知道的人太多, CTF 都不屑于出这类题目。 X-Forwarded-For 诞生的原因比较简单粗暴。 对于一个非常简单的网络模型, 一个网络请求通常只有两方,即请求方与被请求方,如下所示。这样的网络模型下, Web Server 是可以拿到 User 的真实 IP 地址的,即使拿到的可能是路由器的地址。 User --> Web Server 但是上了规模的网站,其网络模型不会这么简单,它可能长这样: User --> CDN --> Web Server 在这种场景下, CDN 依旧可以拿到 User 的真实 IP 地址,然而 Web Server 却无法直接拿到。 为了解决这个问题, 有人提出了 X-Forwarded-For, 它作为 HTTP Header 传递给后端的 Web Server,其格式如下: X-Forwarded-For: 1.0.0.1, 2.0.0.2 细心的朋友可能会发现, 我是不是可以直接将 1.0.0.1 改成任意 IP 地址,然后直接将请求发送给 Web Server?没错,这就是非常简单的 X-Forwarded-For IP 伪造攻击。一般这类问题的解决思路是,校验 4 层协议的来源 IP,判断是否为可信 IP,比如是否为 CDN 的 IP。如果可信,才会尝试解析 X-Fowarded-For Header。 ...
Chrome 反调试之检测 DevTools
0x00 前言 前言的前言 笔者一直认为反调试不是银弹,它只能有限地提升利用成本,并没有本质上的改善 最近的一个项目里,不得不引入一些反调试的手段。因此,笔者对近些年来的 Chrome 反调试技术进行了回顾与测试。 在比较流行的反调试技术里,经常提到的有以下几类: 通过死循环 debugger 让用户无法正常调试 通过 HTML Element 的 id 属性检测 devtools 是否被开启 通过正则表达式的 toString() 方法检测 devtools 是否被开启 方法 1 依旧是简单有效的手段。由于过于简单,容易被发现,一般配合代码混淆以及 eval() 等动态代码执行特性实现。这种方式也容易违反 CSP 。如果没有结合动态代码执行,这个方式很容易被发现并且绕过。 方法 2、3 很不幸,在最新的 Chrome 中已经无法利用。关于 2 ,有个 Chromium 的 issue 记录了这个问题以及它的修复方式。关于 3, 笔者暂时没有没有找到 Chrome 的修复记录。 出于上述原因,笔者不得不找到更多的反调试实现。经过一番测试,找到了一个通过 sourcemap 检测 devtools 的方法。 0x01 SourceMap 检测 DevTools 的实现 关于 SourceMap 及其详细用途可参考 Mozilla 的介绍文档 。 为了加快下载于加载速度,现代网页的 JS 文件普遍被最小化(minified)处理过,去除了不必要的元素,简化了实现,甚至重命名一些变量/函数,提换成更简短的命名方式。这种抠到家的方式,对于开发人员的 debug 和 troubleshooting 是很不利,开发人员自己都读不懂这些 JS 文件。 因此,SourceMap 诞生了。 ...
Web 3 与 Tor
0x00 前言 最近 Web 3 概念炒的火热,即使不想关注,票圈里依旧有许多人在潜移默化地让你关注。上一次这么火爆的概念还是元宇宙。 鄙人不成熟的认知告诉我,一旦一个东西开始大火,火到连菜场大妈都知道的时候,那么这个东西往往没有太大的关注的价值了。毕竟有趣好玩的东西往往掌握在少数人手里。 为了验证我不成熟的认知,我决定花一个小时的时间好好研究一下 Web 3。 0x01 什么是 Web 3 Web3(也被称为Web 3.0,又写为web3)是关于万维网发展的一个概念,主要与基于区块链的去中心化、加密货币以及非同质化代币有关。 – Wikipedia 看起来 Web 3 的定义十分美好,去中心化。然而我注意到, 与区块链有关的web3概念是由以太坊联合创始人Gavin Wood于2014年提出 – Wikipedia 读到这一信息的时候,瞬间就将没了继续研究下去的动力。 几经纠结后,还是决定将 Web 3 关键的亮点看完。关于 Web 3 的关键优势,各个网站有略微不同的说法,比如 Wikipedia 上,认为 Web 3 的优势在于: 将互联网转化为数据库 向人工智能进化的道路 语义网和SOA的实现 向3D进化 等等 鄙人以为,除了第1点,其它优势纯属扯淡。毕竟除了第1点以外,其它优势 Web 2.0 也能实现。 至此, Web 3 的关键亮点可总结为 对等 与 去中心化,以及因此带来的自由(无监管)。 研究到这里,一些人可能已经发现了,Tor、Freenet、I2P 等不都具备上述关键亮点吗? 要是硬是要说出点区别,也是有的: Web 3 基于区块链 Tor、Freenet、I2P 比 Web 3 省资源 (毕竟不需要挖矿,也不需要大量存储区块数据) 比 Web 3 多了匿名特性(也有人认为区块链也是匿名的,这里不争论,你认为是匿名的就是匿名的) 这么一比,Tor、Freenet、I2P 反倒比 Web 3 还好? ...
利用 URN 绕过 URL 检查
0x00 前言 最近痴迷于看 RFC 及各类规范文档,从中发现一些有趣的利用。刚好前段时间发现了一处有趣的特性,成功绕过了某个知名站点的 XSS 防御,最终执行 XSS 攻击。本来以为只是一个小 trick,后来被某偶像拿来出了道 CTF 题目,觉得有必要分享一下。 0x01 一个真实 case 及其绕过 几个月前发现某网站上有类似于下面的逻辑,会从 URL 中取 next 参数的值,并解析出 pathname 部分,执行跳转。 const getParam = (key) => { return new URL(location).searchParams.get(key) } const nextURL = getParam('next') if (nextURL) { const u = new URL(nextURL) location.href = "" + u.pathname } 老司机应当可以发现,如果 u.pathname 能够以 javascript: 开头,那么就可以执行 XSS 攻击了。然而,pathname 会多带一个 ‘/’ ,导致利用失败,无法 XSS。 爱折腾的人怎么会止步于此呢,这个问题的突破点在于 new URL(M).pathname 。根据规范,如果cannot-be-a-base-URL 为 true,那么 pathname 等价于 path[0]。 刚好规范中就给出了满足这类条件的 case,如下图所示 因此,只要构造 ?next=urn:javascript:alert(location.origin) 即可绕过改限制并执行 XSS 攻击。 ...
利用 multipart boundary 绕过 WAF
0x00 前言 WAF(Web Application Firewall)是很常见的 Web 安全基础设施,许多云厂商、大厂、乙方安全公司均有相应的产品。然而,不得不承认,WAF 只能有限提升安全防护能力,不能拦截一些稍微复杂的攻击。正常业务不应当过度依赖 WAF,况且 WAF 还存在误拦截正常业务流量的可能。 目前已知的一些绕过 WAF 的手段包括但不限于: Chunked encoding 绕过 IBM037 等罕见编码绕过 多嘴一句:最早提出 IBM037 编码绕过 WAF 的应该是 Soroush Dalili 在 SteelCon 2017 上的议题,然而国内众多相关文章,基本没有标记出处,很奇怪。 笔者最近在分析 Go 语言的 HTTP 协议解析实现的时候,发现了一种能够利用 multipart boundary 绕过 WAF 的方法,在 Python 的一些 Web 框架上也适用,因而将其分享出来。 0x01 绕过 multipart/form-data 是一种非常常见的 HTML 表单编码方式,绝大部分的 Web 服务器、框架实现,均支持此编码。其编码后的请求大致如下所示,表单数据通过boundary分割。 POST /test HTTP/1.1 Host: example.com Content-Type: multipart/form-data; boundary=“boundary” —boundary Content-Disposition: form-data; name=“field1” value1 —boundary Content-Disposition: form-data; name=“field2”; filename=“example.txt” value2 —boundary— 那么只要满足上述协议要求,服务端就可以正常获取到字段内容了,如下图所示。 ...
构建一个安全可靠的 HTML 富文本过滤器
0x00 前言 许久以前接到一个需求,实现一个 HTML 富文本过滤的基础库。这个需求在其它语言实现中有许多久经考验的开源库,比如 NodeJS 有 DOMPurify ,但在 Go 中却异常尴尬,没有一个合适的、久经考验的 HTML 富文本过滤库。即使运气好找到了一个,也很难保证这个库是安全可靠的。思来想去,还是决定自己做一套性能扛得住、安全可靠的 Go 语言实现。 其实相关代码在 2020 年的时候就已经完成,但一直没有介绍其实现。好在最近开启的 躺平模式 ,终于可以唠一唠这个东西是怎么实现的了。 0x01 原理 咱们的目标是做一套性能扛得住、安全可靠的 Go 语言实现,其核心关键词是性能和安全: 处于安全考虑,这里不能轻易地使用第三方的 DOM 解析库(毕竟也不知道靠不靠谱),最为稳妥的办法是做一个 HTML 的最小语义支持,不管输入如何,这个库只支持它认为正常的HTML 语法。 要满足性能需求,算法复杂度不宜太高,最好是线性扫描 所以最终决定使用 DFA(确定有限状态自动机) 从 0 构建一个 HTML 解析器。 提到 DFA 有些同学可能会一头雾水,但提到正则表达式大家可能会相对熟悉一点。一个正则表达式,可能是一个 DFA,也有可能是一个 NFA(非确定有限状态自动机)。比如 a*ab 这个正则表达式是一个 NFA a+b 这个正则表达式则是一个 DFA 很明显,上面两个正则表达式是等价的,NFA 是可以和 DFA 互转的。 实现具体的 DFA 之前,我们需要先把整个状态机的实现勾勒出来,避免写代码的时候一头雾水。因此,我们按照设想的 “HTML 的最小语义支持”,画了下面这张状态图。 HTML 解析过程的 DFA 源文件 实际上 ETAG_END、TAG_END、NORMAL 是同一种状态,但为了实现方便,这里拆成了三种状态 0x02 实现 安全标签+安全属性 状态机画出来后,还需要总结出所有的安全标签+安全属性。安全标签这个概念比较好理解,类似与 <script> 这种可以造成 XSS 的标签,肯定不属于安全标签。类似的,onerror 这类属性,肯定也不属于安全属性。最终我们梳理出了这么一份安全标签+安全属性列表。 ...
iOS 中利用 Frida 解密任意 APP 的流量
0x00 前言 最近测试的APP里,越来越多的APP采用了加密流量的通信方式,即在原有的HTTP、HTTPS流量之上,又做了一层加密。 要对这些加密过程进行逆向,无疑要耗费大量的工作量,时间可能不允许。因此,可采用一种Hook的方式,将加密前/解密后的流量截获下来。 0x01 原理 通过加密流量与远端进行通信,势必会调用响应的加密、解密函数。因此,可通过Frida直接将未加密的流量Hook出来。 然而这样只能查看未加密的流量,不能篡改里边的数据,显然不太实用。因此,我们需要允许用户在流量进行加密/解密时,篡改里边的内容。 实现思路较为简单,如下所示: Hook加密函数,中断加密过程,将未加密的数据发送到本地搭建的一个服务器,并将HTTP代理设置为Burpsuite的代理 本地搭建的服务器原封不动地返回请求数据 Hook点收到服务器返回的数据后,利用服务器返回的数据替换原有数据,并恢复加密函数的执行过程 重复1、2、3的步骤,Hook掉解密函数 这样一来,Burpsuite就能够发挥其原有的作用,劫持未加密的流量,并对流量内容进行篡改了。 0x02 实现 以某APP为例,首先起一个echo服务器线程,专门负责原封不动地返回客户端的请求数据;其次用Frida hook掉相关加解密函数,将未加密的流量通过Burpsuite代理发往echo服务器。相关脚本内容如下: #!/usr/bin/env python3 # coding: utf-8 from time import sleep from threading import Thread from http.server import HTTPServer, BaseHTTPRequestHandler import sys import requests import frida ECHO_PORT = 28080 BURP_PORT = 8080 class RequestHandler(BaseHTTPRequestHandler): def do_REQUEST(self): content_length = int(self.headers.get('content-length', 0)) self.send_response(200) self.end_headers() self.wfile.write(self.rfile.read(content_length)) do_RESPONSE = do_REQUEST def echo_server_thread(): print('start echo server at port {}'.format(ECHO_PORT)) server = HTTPServer(('', ECHO_PORT), RequestHandler) server.serve_forever() # start echo server first t = Thread(target=echo_server_thread) t.daemon = True t.start() session = frida.get_usb_device().attach('平安普惠') script = session.create_script(''' var reqMethod = ObjC.classes.PHNetworkAgent['- requestOperationWithHTTPMethod:requestSerializer:URLString:parameters:']; var respDecrypt = ObjC.classes.PHSecurityHelper['+ phUnSecurityAESWithAesKey:content:']; var NSString = ObjC.classes.NSString; Interceptor.attach(reqMethod.implementation, { onEnter: function (args) { var methodStr = new ObjC.Object(args[2]).toString(); var urlStr = new ObjC.Object(args[4]).toString(); var param = new ObjC.Object(args[5]); var paramStr = param['- uxy_JSONString']().toString(); var data = { method: methodStr, url: urlStr, param: paramStr, }; send({type: 'REQ', data: data}) var op = recv('NEW_REQ', function(val) { var data = val.payload; args[2] = NSString.stringWithString_(data.method); args[4] = NSString.stringWithString_(data.url); args[5] = NSString.stringWithString_(data.param)['+ __uxy_JSONObject'](); }); op.wait(); } }); Interceptor.attach(respDecrypt.implementation, { onLeave: function (retval) { var resp = new ObjC.Object(retval); var data = resp.toString(); send({type: 'RESP', data: data}) var op = recv('NEW_RESP', function(val) { var data = val.payload; var newRetval = NSString.stringWithString_(data); retval.replace(newRetval); }); op.wait(); } }); ''') def on_message(message, data): if message['type'] == 'send': payload = message['payload'] _type, data = payload['type'], payload['data'] if _type == 'REQ': r = requests.request('REQUEST', 'http://127.0.0.1:{}/'.format(ECHO_PORT), proxies={'http': 'http://127.0.0.1:{}'.format(BURP_PORT)}, data=data['param'].encode('utf-8'), headers={ 'REQ_METHOD': data['method'], 'REQ_URL': data['url'], }) new_data = { 'method': r.headers.get('REQ_METHOD', data['method']), 'url': r.headers.get('REQ_URL', data['url']), 'param': r.text } script.post({'type': 'NEW_REQ', 'payload': new_data}) elif _type == 'RESP': r = requests.request('RESPONSE', 'http://127.0.0.1:{}/'.format(ECHO_PORT), proxies={'http': 'http://127.0.0.1:{}'.format(BURP_PORT)}, data=data.encode('utf-8')) script.post({'type': 'NEW_RESP', 'payload': r.text}) script.on('message', on_message) script.load() sys.stdin.read() 0x03 总结 套用上面的模板,可以快速地对加密流量的APP进行测试。然而实际应用上的难点在于找到相关加解密的函数,对于Objective-C这类没有明显调用关系的APP更甚。 ...
Revel框架的一处DoS问题
0x00 前言 Revel 是一个基于Golang的灵活的Web框架,大量应用了Golang的反射特性,使得其可以类似于Django那样快速地建立一个网站。前段时间在翻阅Revel框架的文档时,发现其某个特性可能存在DoS问题,故对该特性的相关源码进行了审计,发现了一处非常容易利用的DoS问题,利用单个请求即可打挂Revel的服务器。 好在该DoS的利用是有条件的,当且仅当网站使用了Revel框架获取slice类型的参数时才会触发。 0x01 分析 Revel框架为开发者提供了许多有用的特性,其中一个特性允许开发者直接获取数组类型的数据。如当用户访问http://example.com/?keys[]=1&keys[]=2时,开发者可直接将keys参数视为slice类型(Golang中对数组的封装)。 该特性在Revel中是通过反射+Binder实现的,其中专门用于处理slice类型的函数如下。 func bindSlice(params *Params, name string, typ reflect.Type) reflect.Value { // Collect an array of slice elements with their indexes (and the max index). maxIndex := -1 numNoIndex := 0 sliceValues := []sliceValue{} // Factor out the common slice logic (between form values and files). processElement := func(key string, vals []string, files []*multipart.FileHeader) { // ... // 省略相关用于处理单个slice元素的内容 } for key, vals := range params.Values { processElement(key, vals, nil) } for key, fileHeaders := range params.Files { processElement(key, nil, fileHeaders) } resultArray := reflect.MakeSlice(typ, maxIndex+1, maxIndex+1+numNoIndex) for _, sv := range sliceValues { if sv.index != -1 { resultArray.Index(sv.index).Set(sv.value) } else { resultArray = reflect.Append(resultArray, sv.value) } } return resultArray } 该函数的的作用主要用于解析数组类型的参数,进行类型转换并确定slice的最大下标maxIndex,最终申请一个足够大的slice来容纳这些内容。 ...