[^:/\s]+\.[a-zA-Z]{2,})"
36 | webscan_res = json.loads(webscan_res.text)
37 | sites = []
38 | for item in webscan_res:
39 | match = re.search(domains_reg, item["domain"])
40 | if match:
41 | domain = match.group("domain")
42 | sites.append(domain)
43 | return sites
44 | else:
45 | return []
46 | # print(sites)
47 | except Exception as e:
48 | print(f"webscan{e}")
49 | return []
50 | pass
51 |
52 |
53 | def getsite_all(url):
54 | ip138 = getsite_ip138(url)
55 | # webscan = getsite_webscan(url)
56 | # sites = ip138 + webscan
57 | sites = ip138[0]
58 | # print(sites)
59 | return sites
60 |
61 | # print(getsite_all("112.27.219.239"))
62 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | **CharVulFlow_V1.0.1【补天 | 雷神 | 360众包】**
2 |
3 | ```
4 | _________ .__ ____ ____ .__ ___________.__
5 | \_ ___ \| |__ _____ ______\ \ / /_ __| | \_ _____/| | ______ _ __
6 | / \ \/| | \\__ \\_ __ \ Y / | \ | | __) | | / _ \ \/ \/ /
7 | \ \___| Y \/ __ \| | \/\ /| | / |__| \ | |_( <_> ) /
8 | \______ /___| (____ /__| \___/ |____/|____/\___ / |____/\____/ \/\_/
9 | \/ \/ \/ \/
10 | ```
11 |
12 |
13 |
14 |
15 |
16 |
17 | ### 0x00、项目说明
18 | 1. 需求:满足公益漏洞大批量提交需要(尤其针对零权活动),减少人工时间成本;
19 | 2. 支持漏洞类型:GET类型请求,结果可直接回显浏览器(未授权访问、信息泄露、弱口令等);
20 | 3. 支持个性化制:自定义需要截图证明,建议提前与审核商量好提交。
21 | 4. 后续计划加入:权重检测模块,满足日常不参加活动公益漏洞提交。
22 |
23 | ### 0x01、配置文件
24 |
25 | 1. 在module.get_shoot修改适合你电脑的webdrive配置,参考:
26 | - [Edge浏览器配置Selenium](https://blog.csdn.net/tk1023/article/details/109078613)
27 | - Chrome浏览器配置Selenium需修改部分代码
28 | - 第17行代码需要修改为自己的电脑用户名,否则截图浏览器出现弹窗。
29 |
30 |
31 | 2. 在module.config填写需要的cookie以及apikey:
32 | - [补天公益提交入口](https://www.butian.net/Loo/submit/);
33 | - [雷神公益提交入口](https://bug.bountyteam.com/index);
34 | - [360众包提交入口](https://src.360.net/home);
35 | - [站长之家API获取](https://my.chinaz.com/ChinazAPI/DataCenter/MyDataApi);
36 | - [滑块验证API获取](http://rrocr.com/);
37 | ```
38 | [config] # API:站长ICP|滑动验证
39 | chinaz = eaxxxxxxxxxxxxxxxxxxxxxxxxxxx43b
40 | appkey = d2xxxxxxxxxxxxxxxxxxxxxxxxxxx52e
41 |
42 | [but] # 补天
43 | cookie = wzws_sessionid=xxxxxx; PHPSESSID=xxxxxx; __btu__=xxxxxx; __btc__=xxxxxx; __btuc__=xxxxxx
44 |
45 | [thor] # 雷神
46 | cookie = 25xxxxxx-d1b7-4277-a79d-xxxxxxxxxx35
47 |
48 | [bugcloud] # 360众包
49 | cookie = sessionID=xxxxxx; Q_UDID=xxxxxx
50 | ```
51 | 4. 在url.txt填写能回显的所有url,选择注释main函数 **补天 is_but | 雷神 is_thor | 360众包 is_bugcloud** 选择平台运行。
52 |
53 | ### 0x02、使用效果
54 | - jshERP-boot未授权访问为例:直接访问/jshERP-boot/user/getAllList;.ico即可获取所有用户账户密码。
55 | 
56 |
57 | - 程序运行截图
58 |
59 | ```
60 | 0%| | 0/3 [00:00, ?it/s]成功提交金华市xxxxx有限公司
61 | 33%|███▎ | 1/3 [01:05<02:11, 65.51s/it]成功提交苏州xxxxx有限公司
62 | 67%|██████▋ | 2/3 [02:08<01:04, 64.25s/it]成功提交广州xxxxx有限公司
63 | 100%|██████████| 3/3 [03:23<00:00, 67.71s/it]
64 | ```
65 | - 提交成功列表
66 | 
67 | 
68 | - 漏洞详细
69 | 
70 |
71 | ### 0x03、项目逻辑
72 | - 运行流程结构
73 | 
74 |
--------------------------------------------------------------------------------
/module/thor_submit.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from time import sleep
3 | from urllib.parse import urlparse
4 | from tldextract import tldextract
5 | from module.get_icp import get_icp
6 | from module.thor_bypass import thor_bypass
7 | from module.thor_upload import thor_upload
8 |
9 | import json
10 | import requests
11 | import configparser
12 | import os
13 |
14 | # proxies = {'http': 'http://127.0.0.1:8080', 'https': 'https://127.0.0.1:8080'}
15 | # proxies = {}
16 |
17 | BASE_DIR1 = os.path.dirname(os.path.abspath(__file__))
18 | BASE_DIR2 = os.path.dirname(BASE_DIR1)
19 | config = configparser.ConfigParser()
20 | config.read(os.path.join(BASE_DIR1, 'config.ini'))
21 | cookie = config.get('thor', 'cookie')
22 | head = {
23 | 'Host': 'bug.bountyteam.com',
24 | 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36 Edg/115.0.1901.183',
25 | 'Authorization': cookie,
26 | 'Cookie': f"JSESSIONID={cookie}",
27 | 'Acceptlanguage': 'zh',
28 | 'Referer': 'https://bug.bountyteam.com/commitSrcBug'}
29 |
30 |
31 | def thor_submit(url, domain):
32 | # 判断是否存在domain
33 | if len(domain) > 0:
34 | company = get_icp(domain)
35 | else:
36 | company = get_icp(url)
37 | # 判断是否存在公司
38 | if company == []:
39 | return
40 |
41 | url1 = tldextract.extract(url)
42 | host = url1.domain + "." + url1.suffix
43 | image1 = thor_upload(os.path.join(BASE_DIR2, 'temp', 'site.png'))
44 | sleep(5)
45 | image2 = thor_upload(os.path.join(BASE_DIR2, 'temp', 'vul.png'))
46 | sleep(5)
47 | image3 = thor_upload(os.path.join(BASE_DIR2, 'temp', 'icp.png'))
48 | # print("img")
49 | if len(domain) > 0:
50 | image4 = thor_upload(os.path.join(BASE_DIR2, 'temp', 'domain.png'))
51 | whois = f'''
52 | [[§p§]]IP查询[[§/p§]][[§p§]][[§img src="{image4}"§]][[§br§]][[§/p§]]
53 | [[§p§]]ICP查询[[§/p§]][[§p§]][[§img src="{image3}"§]][[§br§]][[§/p§]]'''
54 | else:
55 | whois = f'''
56 | [[§p§]]ICP查询[[§/p§]][[§p§]][[§img src="{image3}"§]][[§br§]][[§/p§]]'''
57 | # 提交前获取验证识别
58 | code = thor_bypass()
59 | # print("bypass")
60 | data = {"companyName": company,
61 | "companyIp": ["36.134.45.156"],
62 | "companyDomainName": [host],
63 | "holeTitle": f"{company}敏感信息泄露",
64 | "industryCode": "000050",
65 | "provinceCode": "370000",
66 | "cityCode": "371500",
67 | "selfEvaluationTime": "",
68 | "selfEvaluationLevel": 2,
69 | "holeType": "信息泄露",
70 | "holeUrl": [url],
71 | "holeDetail": f'''
72 | [[§p§]]### 归属证明:[[§/p§]]
73 | {whois}
74 | [[§p§]]### 复现步骤:[[§/p§]]
75 | [[§p§]]1.网站首页[[§/p§]][[§p§]][[§img src="{image1}"§]][[§br§]][[§/p§]]
76 | [[§p§]]2.直接访问漏洞url即可发现敏感文件已下载,看浏览器右上角,打开如下[[§/p§]]
77 | [[§p§]]漏洞所在[[§/p§]][[§p§]][[§img src="{image2}"§]][[§br§]][[§/p§]]
78 | ''',
79 | "repairPropose": "鉴权过滤",
80 | "verifyCode": code,
81 | "holeDesc": "",
82 | "holeHarm": "",
83 | "userId": 1367
84 | }
85 |
86 | try:
87 | url1 = "https://bug.bountyteam.com/api/hole/add"
88 | res1 = requests.post(url=url1, headers=head, json=data, verify=False)
89 | # print(res1.text)
90 | except Exception as e:
91 | print(e)
92 | data = json.loads(res1.text)
93 | if data["code"] == 200:
94 | print(f"雷神成功提交{company}")
95 | # 验证失败
96 | elif data["code"] == 201:
97 | return thor_submit(url, domain)
98 | else:
99 | print(data["message"])
100 |
101 | # url = ""
102 | # domain = ""
103 | # thor_submit(url, domain)
104 |
--------------------------------------------------------------------------------
/module/bugcloud_submit.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from time import sleep
3 | from urllib.parse import urlparse
4 | from tldextract import tldextract
5 | from module.get_icp import get_icp
6 | from module.bugcloud_upload import bugcloud_upload
7 |
8 | import json
9 | import requests
10 | import configparser
11 | import os
12 |
13 | # proxies = {'http': 'http://127.0.0.1:8080', 'https': 'https://127.0.0.1:8080'}
14 | proxies = {}
15 |
16 | BASE_DIR1 = os.path.dirname(os.path.abspath(__file__))
17 | BASE_DIR2 = os.path.dirname(BASE_DIR1)
18 | config = configparser.ConfigParser()
19 | config.read(os.path.join(BASE_DIR1, 'config.ini'))
20 | cookie = config.get('bugcloud', 'cookie')
21 | head = {
22 | 'Host': 'src.360.net',
23 | 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36 Edg/115.0.1901.183',
24 | 'Cookie': f"{cookie}",
25 | 'Acceptlanguage': 'zh',
26 | 'Referer': 'https://src.360.net/submit-bug'}
27 |
28 | def bugcloud_csrf():
29 | url2 = "https://src.360.net//api/frontend/user/userdetail"
30 | try:
31 | res2 = requests.post(url=url2, headers=head, verify=False)
32 | except Exception as e:
33 | print(e)
34 | data = json.loads(res2.text)
35 | data = data["result"]["csrf_token"]
36 | return data
37 |
38 | # print(bugcloud_csrf())
39 |
40 | def bugcloud_submit(url, domain):
41 | # 判断是否存在domain
42 | if len(domain) > 0:
43 | company = get_icp(domain)
44 | else:
45 | company = get_icp(url)
46 | # print(company)
47 | # 判断是否存在公司
48 | if company == []:
49 | return
50 |
51 | url1 = tldextract.extract(url)
52 | host = url1.domain + "." + url1.suffix
53 | image1 = bugcloud_upload(os.path.join(BASE_DIR2, 'temp', 'site.png'))
54 | sleep(5)
55 | image2 = bugcloud_upload(os.path.join(BASE_DIR2, 'temp', 'vul.png'))
56 | sleep(5)
57 | image3 = bugcloud_upload(os.path.join(BASE_DIR2, 'temp', 'icp.png'))
58 | # print("img")
59 | if len(domain) > 0:
60 | image4 = bugcloud_upload(os.path.join(BASE_DIR2, 'temp', 'domain.png'))
61 | whois = f'''
62 | IP查询:
归属查询:
'''
63 | else:
64 | whois = f'''
65 | 归属查询:
'''
66 | # print(whois)
67 | # 提交前获取验证识别
68 | csrf = bugcloud_csrf()
69 | # print("csrf")
70 | data = {"business_name": f"{company}所属网站",
71 | "title": f"{company}敏感信息泄露",
72 | "bug_type": "1",
73 | "bug_level": "5",
74 | "desc_v": f"{company}敏感信息泄露",
75 | "rec_step": f'''归属证明:{whois}
1.网站首页:
2.漏洞截图:
''',
76 | "repair_plan": "鉴权过滤,限制接口访问,及时更新
",
77 | "annex": {},
78 | "web_name": f"{company}",
79 | "web_ip": f"{domain}",
80 | "province": "北京市",
81 | "city": "北京市",
82 | "area": "市辖区",
83 | "bug_category_main": "1",
84 | "bug_category": "45",
85 | "bug_url": f"{url}",
86 | "csrf_token": f"{csrf}",
87 | "is_use_common_bug": "5",
88 | "component_name": "",
89 | "first_industry": "1",
90 | "second_industry": "122",
91 | # "bug_id": "7lYPXe4aG/k="
92 | }
93 | # print(data)
94 | try:
95 | url1 = "https://src.360.net/api/frontend/hacker/bugmanage/submitbug"
96 | res1 = requests.post(url=url1, headers=head, json=data, verify=False, proxies=proxies)
97 | # print(res1.text)
98 | except Exception as e:
99 | print(e)
100 | data = json.loads(res1.text)
101 | if data["code"] == 200:
102 | print(f"360众包成功提交{company}")
103 | # 验证失败
104 | elif data["code"] == 201:
105 | return bugcloud_submit(url, domain)
106 | else:
107 | print(data["msg"])
108 |
109 | # url = "http://1.14.8.252:8088/808gps/MobileAction_downLoad.action?path=/WEB-INF/classes/config/jdbc.properties"
110 | # domain = "mingshang.cc"
111 | # bugcloud_submit(url, domain)
112 |
--------------------------------------------------------------------------------
/module/get_shoot.py:
--------------------------------------------------------------------------------
1 | from urllib.parse import urlparse
2 | from selenium import webdriver
3 | from selenium.webdriver.common.by import By
4 | from time import sleep
5 | from PIL import ImageGrab
6 | import os
7 |
8 | BASE_DIR1 = os.path.dirname(os.path.abspath(__file__))
9 | BASE_DIR2 = os.path.dirname(BASE_DIR1)
10 |
11 |
12 | def shoot_noip(url):
13 | bbox = (0, 0, 1920, 1080)
14 | options = webdriver.EdgeOptions()
15 | options.add_experimental_option('excludeSwitches', ['enable-automation'])
16 | options.add_argument('--ignore-certificate-errors')
17 | options.add_argument('--user-data-dir=C:\\Users\\hushuang1\\AppData\\Local\\Microsoft\\Edge\\User Data\\Default')
18 | driver = webdriver.Edge(options=options)
19 | # driver.get(r'https://www.baidu.com/')
20 | driver.maximize_window()
21 |
22 | result = urlparse(url)
23 | # **********截图icp备案**********
24 | try:
25 | url1 = "https://icp.chinaz.com/" + result.netloc
26 | driver.get(url1)
27 | except Exception as e:
28 | driver.quit()
29 | return "shibai"
30 | sleep(3)
31 | im = ImageGrab.grab(bbox)
32 | im.save(os.path.join(BASE_DIR2, 'temp', 'icp.png'))
33 |
34 | # **********截图主页**********
35 | try:
36 | url2 = result.scheme + "://" + result.netloc
37 | driver.get(url2)
38 | except Exception as e:
39 | driver.quit()
40 | return "shibai"
41 | sleep(3)
42 | im = ImageGrab.grab(bbox)
43 | im.save(os.path.join(BASE_DIR2, 'temp', 'site.png'))
44 |
45 | # **********截图漏洞**********
46 | try:
47 | url3 = url
48 | driver.get(url3)
49 | except Exception as e:
50 | driver.quit()
51 | return "shibai"
52 | sleep(3)
53 | im = ImageGrab.grab(bbox)
54 | im.save(os.path.join(BASE_DIR2, 'temp', 'vul.png'))
55 |
56 | sleep(2)
57 | driver.quit()
58 |
59 |
60 | # shoot_noip("https://www.mingshang.cc:443/808gps/MobileAction_downLoad.action?path=/WEB-INF/classes/config/jdbc.properties")
61 |
62 | def shoot_isip(url, domain):
63 | bbox = (0, 0, 1920, 1080)
64 | options = webdriver.EdgeOptions()
65 | options.add_experimental_option('excludeSwitches', ['enable-automation'])
66 | options.add_argument('--ignore-certificate-errors')
67 | options.add_argument('--user-data-dir=C:\\Users\\hushuang1\\AppData\\Local\\Microsoft\\Edge\\User Data\\Default')
68 | driver = webdriver.Edge(options=options)
69 | # driver.get(r'https://www.baidu.com/')
70 | driver.maximize_window()
71 |
72 | # **********截图icp备案**********
73 | if url.startswith("http"):
74 | domain = domain
75 | else:
76 | domain = f"http://{domain}"
77 | try:
78 | result = urlparse(domain)
79 | # print(result)
80 | driver.get("https://icp.chinaz.com/" + result.netloc + result.path)
81 | except Exception as e:
82 | driver.quit()
83 | return "shibai"
84 | sleep(3)
85 | im = ImageGrab.grab(bbox)
86 | im.save(os.path.join(BASE_DIR2, 'temp', 'icp.png'))
87 | result = urlparse(url)
88 | # print(result)
89 |
90 | # **********截图ip反查**********
91 | try:
92 | url1 = result.netloc
93 | url1 = url1.split(":")[0]
94 | driver.get("https://site.ip138.com/" + url1)
95 | except Exception as e:
96 | sleep(3)
97 | sleep(3)
98 | im = ImageGrab.grab(bbox)
99 | im.save(os.path.join(BASE_DIR2, 'temp', 'domain.png'))
100 |
101 | # **********截图主页**********
102 | try:
103 | # print(result)
104 | url2 = result.scheme + "://" + result.netloc
105 | driver.get(url2)
106 | except Exception as e:
107 | driver.quit()
108 | # print(f"{e}site")
109 | return "shibai"
110 | sleep(3)
111 | im = ImageGrab.grab(bbox)
112 | im.save(os.path.join(BASE_DIR2, 'temp', 'site.png'))
113 |
114 | # **********截图漏洞**********
115 | try:
116 | url3 = url
117 | driver.get(url3)
118 | except Exception as e:
119 | driver.quit()
120 | return "shibai"
121 | sleep(3)
122 | im = ImageGrab.grab(bbox)
123 | im.save(os.path.join(BASE_DIR2, 'temp', 'vul.png'))
124 |
125 | sleep(2)
126 | driver.quit()
127 | driver.close()
128 |
129 | # shoot_isip("http://1.14.8.252:8088/808gps/MobileAction_downLoad.action?path=/WEB-INF/classes/config/jdbc.properties", "xjrjyy.cn")
130 |
--------------------------------------------------------------------------------
/module/but_submit.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from time import sleep
3 | from urllib.parse import urlparse
4 | from tldextract import tldextract
5 | from module.get_icp import get_icp
6 | from module.but_bypass import but_bypass
7 | from module.but_upload import but_upload
8 |
9 | import random
10 | import json
11 | import requests
12 | import configparser
13 | import os
14 |
15 | requests.packages.urllib3.disable_warnings()
16 | BASE_DIR1 = os.path.dirname(os.path.abspath(__file__))
17 | BASE_DIR2 = os.path.dirname(BASE_DIR1)
18 | config = configparser.ConfigParser()
19 | config.read(os.path.join(BASE_DIR1, 'config.ini'))
20 | cookie = config.get('but', 'cookie')
21 |
22 | session = requests.session()
23 | headers = {
24 | 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36 Edg/115.0.1901.183',
25 | 'Cookie': f'{cookie}'}
26 |
27 | # proxies = {'http': 'http://127.0.0.1:8080', 'https': 'https://127.0.0.1:8080'}
28 | proxies = {}
29 |
30 |
31 | def but_submit(url, domain):
32 | if len(domain) > 0:
33 | company = get_icp(domain)
34 | else:
35 | company = get_icp(url)
36 | # print(company)
37 | if company == []:
38 | return
39 | # 获取主页截图上传
40 | url1 = tldextract.extract(url)
41 | host = url1.domain + "." + url1.suffix
42 |
43 | image1 = but_upload(os.path.join(BASE_DIR2, 'temp', 'site.png'))
44 | image1 = image1.replace("\/", "/")
45 | sleep(5)
46 | image2 = but_upload(os.path.join(BASE_DIR2, 'temp', 'vul.png'))
47 | image2 = image2.replace("\/", "/")
48 | sleep(5)
49 | image3 = but_upload(os.path.join(BASE_DIR2, 'temp', 'icp.png'))
50 | image3 = image3.replace("\/", "/")
51 |
52 | if len(domain) > 0:
53 | image4 = but_upload(os.path.join(BASE_DIR2, 'temp', 'domain.png'))
54 | image4 = image4.replace("\/", "/")
55 | whois = f'''
56 |
57 |
'''
58 | else:
59 | whois = f'''
60 |
'''
61 | # 滑块识别
62 | challenge, validate = but_bypass()
63 | if (challenge == "shibai"):
64 | return
65 | detail = f"""归属证明截图:{whois}
66 | 访问首页截图:
67 | 漏洞所在url:{url}
68 |
69 | 漏洞验证步骤:
-
70 |
直接访问漏洞url即可下载数据库配置文件,观察浏览器右上角发现已经下载
71 | 配置文件截图:
72 | """
73 |
74 | files = {
75 | 'attachment': (None, ''),
76 | 'attachment_name': (None, ''),
77 | 'url': (None, url),
78 | 'attribute': (None, '1'), # 漏洞类型,1:事件 2:通用
79 | 'company_name': (None, company),
80 | 'host': (None, host),
81 | 'origin': (None, '1'),
82 | 'title': (None, company + '1网站存在数据库配置信息泄露'),
83 | 'active_id': (None, ''), # 活动id
84 | 'type': (None, '10'), # 漏洞类型 67弱口令 10信息泄露
85 | 'level': (None, '1'), # 漏洞等级 2:高危
86 | 'description': (None, company + '关联网站存在数据库账号密码明文信息泄露'),
87 | 'detail': (None, detail),
88 | 'repair_suggest': (None, '加强拦截策略,不允许接口访问'),
89 | 'tag3': (None, 'class1|18,class2|19'),
90 | 'province': (None, '青海省'), # 所属地区 省
91 | 'city': (None, '西宁市'), # 所属地区 市
92 | 'county': (None, '市辖区'),
93 | 'company_contact': (None, ''),
94 | 'anonymous': (None, '1'), # 匿名提交
95 | 'agree': (None, '1'), # 是否同意用户协议
96 | 'id': (None, ''), # 未知属性
97 | 'geetest_challenge': (None, challenge),
98 | 'geetest_validate': (None, validate),
99 | 'geetest_seccode': (None, validate + '|jordan')
100 | }
101 | try:
102 | url_but = "https://www.butian.net/Home/Loo/submit"
103 | rsp = session.post(url=url_but, files=files, headers=headers, verify=False,
104 | timeout=15, proxies=proxies)
105 | except:
106 | return but_submit(url, domain)
107 | # print(rsp.text)
108 | data = json.loads(rsp.text)
109 | if "QTVA" in data["data"]:
110 | print(f"补天成功提交{company}")
111 | pass
112 | elif data["info"] == "\u64cd\u4f5c\u8fc7\u4e8e\u9891\u7e41\uff0c\u8bf7\u7a0d\u540e\u518d\u8bd5":
113 | # print(f"{url}操作过于频繁")
114 | sleep(random.randint(20, 30))
115 | but_submit(url, domain)
116 | elif rsp.status_code == 302:
117 | print(f"补天COOKIE失效")
118 | pass
119 | else:
120 | print(f'补天{data["info"]}')
121 | pass
122 |
123 | # but_submit("http://1.14.8.252:8088/808gps/MobileAction_downLoad.action?path=/WEB-INF/classes/config/jdbc.properties",
124 | # "mingshang.cc")
125 |
--------------------------------------------------------------------------------