原文链接:我的费用报告导致 Lyft 上的服务器端请求伪造 (SSRF) — 纳哈姆Sec (nahamsec.com)
在一次会议之旅中,我发现Lyft应用程序允许用户通过将商务行程历史记录导出为PDF或CSV文件来创建费用报告。作为一个活跃的Lyft用户,这对我来说是个好消息,因为它简化了单调乏味的差旅费用流程,使我的生活更轻松。但它也提出了一个问题:“我可以破解这个东西吗?”结果,答案是肯定的。
完成行程并为司机评分或给小费后,系统将提示您下图,该图像允许您附加费用并附上注释。当然,当我在机场结束行程时,我为我的费用信息放置了一个 HTML标签,使我能够在 Lyft app 的“行程历史记录”选项卡下访问一个全新的界面。它向我展示了一个部分,它允许我选择要导出到费用兑换中的游乐设施。一旦我选择了我的行程来支付我的费用,Lyft应用程序就会发出一封电子邮件,我收到了两种格式的费用:CVS和PDF。通过打开PDF,我能够确认放置在“费用说明”中的html标签(<h1>test)已成功呈现在PDF中:
这立即引起了我的注意。我想看看我是否能够利用PDF生成器,SSRF是有可能的结果
一旦我们确认我们可以将HTML插入到PDF中,下一步就是看看我们是否真的可以让应用程序获取外部资源来收集信息(例如User-Agent),这将有助于我们更好地理解应用程序。请记住,这也要求我们每次尝试有效载荷时都要乘坐Lyft。我们专门花了一些时间来获取用户代理,方法是强制PDF从我们控制的Web服务器获取远程文件,使用<iframe>和<img>等标签。但是,不幸的是,我们当时无法获得任何这些信息。
几周后,HackerOne在纽约举办了一场现场黑客活动,这使我们能够使用Lyft应用程序进行大量行程,这是一个重新审视这个bug的好机会。我们这次的重点是了解为什么某些标签有效。如前所述,电子邮件还包含一个CSV文件,该文件公开了设置为费用代码的确切字符串,而不对其进行呈现。“看到这里,我们看到我们的手机拼写错误输入了有效负载,'左/右双引号'与常规引号一旦我们在原始有效负载中修复了这个问题,我们就骑了一趟,在那里我们能够获得PDF生成器的User-Agent,它将我们的重点从Lyft的应用程序转移到了WeasyPrint。
<h1> or <u><img><iframe>“"
https://weasyprint.org/
是一种智能解决方案,可帮助网络开发人员创建PDF文档。它将简单的HTML页面变成华丽的发票,门票,统计报告...事实证明,它也是开源的。使用
youtube的使用视频
https://youtu.be/t5fB6OZsR6c
以下是weasyprint的工作原理,它采用一个html模板并从中创建一个PDF。您可以使用以下命令在本地执行此操作:
$> weasyprint input.html output.pdf
在本地安装它的实例后,我们能够了解它是如何工作的。这使得我们的测试变得容易得多,因为我们不再需要为了测试我们的有效载荷而乘坐。经过几次尝试而没有查看源代码,我们对 WeasyPrint 的工作原理有了初步的了解:
它允许少量的 HTML 标记
不允许使用脚本
不允许使用 iframe 或类似标签
https://github.com/Kozea/WeasyPrint/blob/b7a9fe7dcc9d0755a3324b74d0965e806bb87378/weasyprint/html.py
在我们查看了一些文件并在上面链接中发现了一些有趣的东西之后,WeasyPrint重新定义了一组html标签,包括img,嵌入,对象等。根据我们之前的测试,我们已经知道javascript不是一个利用它的选项。在这一点上,我们的希望很低,我们开始认为PDF生成器不再可利用,直到在下面的几个文件中发现了对<link>的引用。这使我们能够通过使用 将任何网页或本地文件的内容附加到我们的PDF中。
https://github.com/Kozea/WeasyPrint/blob/b7a9fe7dcc9d0755a3324b74d0965e806bb87378/weasyprint/pdf.py
<link rel=attachment href="file:///root/secret.txt">
使用zlib和python,我们创建了一个脚本,帮助我们从pdf中解压缩本地文件的内容。
import sys, zlib
def main(fn):
data = open(fn, 'rb').read()
i = 0
first = True
last = None
while True:
i = data.find(b'>>\nstream\n', i)
if i == -1:
break
i += 10
try:
last = cdata = zlib.decompress(data[i:])
if first:
first = False
else:
pass#print cdata
except:
pass
print(last.decode('utf-8'))
if __name__=='__main__':
main(*sys.argv[1:])
这为我们提供了本地主机上的工作POC,因此我们使用Lyft应用程序进行了最后一次骑行来测试我们的有效载荷,并且我们能够确认此错误的存在。
往期文章推荐