Stay Young, Stay Naive.

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 的行为为何如此奇怪,本人并没有了解清楚,有待研究。 ...

June 17, 2024 · 1 min · 84 words · SYM01

再谈 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。 ...

June 17, 2024 · 3 min · 546 words · SYM01

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 诞生了。 ...

May 6, 2023 · 1 min · 168 words · SYM01

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 还好? ...

May 3, 2022 · 1 min · 179 words · SYM01

利用 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 攻击。 ...

August 22, 2021 · 1 min · 92 words · SYM01

利用 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— 那么只要满足上述协议要求,服务端就可以正常获取到字段内容了,如下图所示。 ...

August 2, 2021 · 1 min · 182 words · SYM01

构建一个安全可靠的 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 这类属性,肯定也不属于安全属性。最终我们梳理出了这么一份安全标签+安全属性列表。 ...

June 5, 2021 · 2 min · 227 words · SYM01

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更甚。 ...

November 4, 2019 · 2 min · 282 words · SYM01

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来容纳这些内容。 ...

March 4, 2019 · 1 min · 155 words · SYM01