使用C#winform編寫滲透測試工具--子域名挖掘

這篇文章主要介紹使用C#winform編寫滲透測試工具--子域名挖掘。在滲透測試中,子域名的收集十分重要,通常一個網站的主站的防禦能力特別強,而他們的非主站相對較弱,我們便可以通過收集子站資訊從而擴大攻擊範圍,增大滲透的可能。

  • 下面是使用C#winform編寫的滲透測試工具,前面我們已經完成了埠掃描、敏感目錄掃描和暴力破解的工作,這一部分將介紹如何實現子域名挖掘。


目錄

  1. 各種子域名挖掘技術
  2. 程式碼實現
  3. 使用步驟

一、各種子域名挖掘技術

字典爆破

  • 字典爆破就是通過收集來的字典,拼接到頂級域名前面,然後通過自動化工具進行訪問,判斷返回結果,從而跑出子域名是否存在。比如ESDsubDomainsBrute

證書SSL查詢

  • 因為SSL證書支援證書透明度,而SSL裡包含子域名。證書SSL查詢就是通過HTTPS 證書,ssl證書等蒐集子域名記錄。比如網站cet就是從SSL證書收集子域名。

DNS資料

  • DNS原理就是蒐集DNS的解析歷史,通過查詢dns記錄來獲取到對方的解析記錄,從而獲取到子域名,正常來說你的域名經DNS解析過一般就會搜到。比如virustotal執行DNS解析來構建資料庫,來檢索子域名。

爬蟲提取子域名(js檔案提取)

  • 利用爬蟲從頁面原始碼中提取子域名,比如JSFinder

二、程式碼實現

這裡分別使用兩種方式實現子域名的挖掘,即通過證書SSL查詢和js檔案提取。

1.使用證書SSL查詢方式

參考文章漏洞挖掘-子域名,從線上網站crt中直接提取相關的子域名。

  1. # !/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. import sys
  4. import urllib.request
  5. import urllib.parse
  6. import re
  7. import ssl
  8. ssl._create_default_https_context = ssl._create_unverified_context
  9. def crt_domain(domains):
  10. headers = {
  11. 'User-Agent': 'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6'
  12. }
  13. with urllib.request.urlopen('https://crt.sh/?q=' + domains) as f:
  14. code = f.read().decode('utf-8')
  15. for cert, domain in re.findall('<tr>(?:\s|\S)*?href="\?id=([0-9]+?)"(?:\s|\S)*?<td>([*_a-zA-Z0-9.-]+?\.' + re.escape(domains) + ')</td>(?:\s|\S)*?</tr>', code, re.IGNORECASE):
  16. domain = domain.split('@')[-1]
  17. print(domain)
  18. with open('crt_result.txt', 'a+') as f:
  19. f.write(str(domain)+'\n')
  20. if __name__ == '__main__':
  21. if len(sys.argv) == 2:
  22. domains=sys.argv[1]
  23. crt_domain(domains[11:])
  24. else:
  25. print('User: python3 crt_domain.py domain')

C#呼叫指令碼

對於python指令碼中包含第三方模組的情況,同樣,通過直接建立Process程序,呼叫python指令碼,返回掃描結果。

  • 建立按鈕按下事件button1_Click,執行“呼叫python指令碼”函式runPythonSubdomain_ssl()
  1. private void button9_Click(object sender, EventArgs e)
  2. {
  3. richTextBox4.Clear();
  4. runPythonSubdomain_ssl();//執行python函式
  5. label22.Text = "開始掃描...";
  6. }
  • 例項化一個python程序 呼叫.py 指令碼
  1. void runPythonSubdomain_ssl()
  2. {
  3. string url = textBox9.Text;
  4. p = new Process();
  5. string path = "Subdomain.py";//待處理python檔案的路徑,本例中放在debug資料夾下
  6. string sArguments = path;
  7. ArrayList arrayList = new ArrayList();
  8. arrayList.Add(url);//需要挖掘的域名
  9. foreach (var param in arrayList)//拼接引數
  10. {
  11. sArguments += " " + param;
  12. }
  13. p.StartInfo.FileName = @"D:\Anaconda\python.exe"; //沒有配環境變數的話,可以寫"xx\xx\python.exe"的絕對路徑。如果配了,直接寫"python"即可
  14. p.StartInfo.Arguments = sArguments;//python命令的引數
  15. p.StartInfo.UseShellExecute = false;
  16. p.StartInfo.RedirectStandardOutput = true;
  17. p.StartInfo.RedirectStandardInput = true;
  18. p.StartInfo.RedirectStandardError = true;
  19. p.StartInfo.CreateNoWindow = true;
  20. p.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
  21. p.Start();//啟動程序
  22. //MessageBox.Show("啟動成功");
  23. p.BeginOutputReadLine();
  24. p.OutputDataReceived += new DataReceivedEventHandler(p_OutputDataReceived_subdomain_ssl);
  25. Console.ReadLine();
  26. //p.WaitForExit();
  27. }
  28. void p_OutputDataReceived_subdomain_ssl(object sender, DataReceivedEventArgs e)
  29. {
  30. var printedStr = e.Data;
  31. Action at = new Action(delegate ()
  32. {
  33. //接受.py程序列印的字元資訊到文字顯示框
  34. richTextBox4.AppendText(printedStr + "\n");
  35. label22.Text = "掃描結束";
  36. });
  37. Invoke(at);
  38. }

2.使用js檔案提取

  1. #!/usr/bin/env python"
  2. # coding: utf-8
  3. import requests, argparse, sys, re
  4. from requests.packages import urllib3
  5. from urllib.parse import urlparse
  6. from bs4 import BeautifulSoup
  7. def parse_args():
  8. parser = argparse.ArgumentParser(epilog='\tExample: \r\npython ' + sys.argv[0] + " -u http://www.baidu.com")
  9. parser.add_argument("-u", "--url", help="The website")
  10. parser.add_argument("-c", "--cookie", help="The website cookie")
  11. parser.add_argument("-f", "--file", help="The file contains url or js")
  12. parser.add_argument("-ou", "--outputurl", help="Output file name. ")
  13. parser.add_argument("-os", "--outputsubdomain", help="Output file name. ")
  14. parser.add_argument("-j", "--js", help="Find in js file", action="store_true")
  15. parser.add_argument("-d", "--deep",help="Deep find", action="store_true")
  16. return parser.parse_args()
  17. def extract_URL(JS):
  18. pattern_raw = r"""
  19. (?:"|') # Start newline delimiter
  20. (
  21. ((?:[a-zA-Z]{1,10}://|//) # Match a scheme [a-Z]*1-10 or //
  22. [^"'/]{1,}\. # Match a domainname (any character + dot)
  23. [a-zA-Z]{2,}[^"']{0,}) # The domainextension and/or path
  24. |
  25. ((?:/|\.\./|\./) # Start with /,../,./
  26. [^"'><,;| *()(%%$^/\\\[\]] # Next character can't be...
  27. [^"'><,;|()]{1,}) # Rest of the characters can't be
  28. |
  29. ([a-zA-Z0-9_\-/]{1,}/ # Relative endpoint with /
  30. [a-zA-Z0-9_\-/]{1,} # Resource name
  31. \.(?:[a-zA-Z]{1,4}|action) # Rest + extension (length 1-4 or action)
  32. (?:[\?|/][^"|']{0,}|)) # ? mark with parameters
  33. |
  34. ([a-zA-Z0-9_\-]{1,} # filename
  35. \.(?:php|asp|aspx|jsp|json|
  36. action|html|js|txt|xml) # . + extension
  37. (?:\?[^"|']{0,}|)) # ? mark with parameters
  38. )
  39. (?:"|') # End newline delimiter
  40. """
  41. pattern = re.compile(pattern_raw, re.VERBOSE)
  42. result = re.finditer(pattern, str(JS))
  43. if result == None:
  44. return None
  45. js_url = []
  46. return [match.group().strip('"').strip("'") for match in result
  47. if match.group() not in js_url]
  48. # 傳送請求
  49. def Extract_html(URL):
  50. header = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.108 Safari/537.36",
  51. "Cookie": args.cookie}
  52. try:
  53. raw = requests.get(URL, headers = header, timeout=3, verify=False)
  54. raw = raw.content.decode("utf-8", "ignore")
  55. return raw
  56. except:
  57. return None
  58. # 處理url
  59. def process_url(URL, re_URL):
  60. black_url = ["javascript:"] # Add some keyword for filter url.
  61. URL_raw = urlparse(URL)
  62. ab_URL = URL_raw.netloc
  63. host_URL = URL_raw.scheme
  64. if re_URL[0:2] == "//":
  65. result = host_URL + ":" + re_URL
  66. elif re_URL[0:4] == "http":
  67. result = re_URL
  68. elif re_URL[0:2] != "//" and re_URL not in black_url:
  69. if re_URL[0:1] == "/":
  70. result = host_URL + "://" + ab_URL + re_URL
  71. else:
  72. if re_URL[0:1] == ".":
  73. if re_URL[0:2] == "..":
  74. result = host_URL + "://" + ab_URL + re_URL[2:]
  75. else:
  76. result = host_URL + "://" + ab_URL + re_URL[1:]
  77. else:
  78. result = host_URL + "://" + ab_URL + "/" + re_URL
  79. else:
  80. result = URL
  81. return result
  82. def find_last(string,str):
  83. positions = []
  84. last_position=-1
  85. while True:
  86. position = string.find(str,last_position+1)
  87. if position == -1:break
  88. last_position = position
  89. positions.append(position)
  90. return positions
  91. def find_by_url(url, js = False):
  92. if js == False:
  93. try:
  94. print("url:" + url)
  95. except:
  96. print("Please specify a URL like https://www.baidu.com")
  97. html_raw = Extract_html(url)
  98. if html_raw == None:
  99. print("Fail to access " + url)
  100. return None
  101. #print(html_raw)
  102. html = BeautifulSoup(html_raw, "html.parser")
  103. html_scripts = html.findAll("script")
  104. script_array = {}
  105. script_temp = ""
  106. for html_script in html_scripts:
  107. script_src = html_script.get("src")
  108. if script_src == None:
  109. script_temp += html_script.get_text() + "\n"
  110. else:
  111. purl = process_url(url, script_src)
  112. script_array[purl] = Extract_html(purl)
  113. script_array[url] = script_temp
  114. allurls = []
  115. for script in script_array:
  116. #print(script)
  117. temp_urls = extract_URL(script_array[script])
  118. if len(temp_urls) == 0: continue
  119. for temp_url in temp_urls:
  120. allurls.append(process_url(script, temp_url))
  121. result = []
  122. for singerurl in allurls:
  123. url_raw = urlparse(url)
  124. domain = url_raw.netloc
  125. positions = find_last(domain, ".")
  126. miandomain = domain
  127. if len(positions) > 1:miandomain = domain[positions[-2] + 1:]
  128. #print(miandomain)
  129. suburl = urlparse(singerurl)
  130. subdomain = suburl.netloc
  131. #print(singerurl)
  132. if miandomain in subdomain or subdomain.strip() == "":
  133. if singerurl.strip() not in result:
  134. result.append(singerurl)
  135. return result
  136. return sorted(set(extract_URL(Extract_html(url)))) or None
  137. def find_subdomain(urls, mainurl):
  138. url_raw = urlparse(mainurl)
  139. domain = url_raw.netloc
  140. miandomain = domain
  141. positions = find_last(domain, ".")
  142. if len(positions) > 1:miandomain = domain[positions[-2] + 1:]
  143. subdomains = []
  144. for url in urls:
  145. suburl = urlparse(url)
  146. subdomain = suburl.netloc
  147. #print(subdomain)
  148. if subdomain.strip() == "": continue
  149. if miandomain in subdomain:
  150. if subdomain not in subdomains:
  151. subdomains.append(subdomain)
  152. return subdomains
  153. def find_by_url_deep(url):
  154. html_raw = Extract_html(url)
  155. if html_raw == None:
  156. print("Fail to access " + url)
  157. return None
  158. html = BeautifulSoup(html_raw, "html.parser")
  159. html_as = html.findAll("a")
  160. links = []
  161. for html_a in html_as:
  162. src = html_a.get("href")
  163. if src == "" or src == None: continue
  164. link = process_url(url, src)
  165. if link not in links:
  166. links.append(link)
  167. if links == []: return None
  168. print("ALL Find " + str(len(links)) + " links")
  169. urls = []
  170. i = len(links)
  171. for link in links:
  172. temp_urls = find_by_url(link)
  173. if temp_urls == None: continue
  174. print("Remaining " + str(i) + " | Find " + str(len(temp_urls)) + " URL in " + link)
  175. for temp_url in temp_urls:
  176. if temp_url not in urls:
  177. urls.append(temp_url)
  178. i -= 1
  179. return urls
  180. def find_by_file(file_path, js=False):
  181. with open(file_path, "r") as fobject:
  182. links = fobject.read().split("\n")
  183. if links == []: return None
  184. print("ALL Find " + str(len(links)) + " links")
  185. urls = []
  186. i = len(links)
  187. for link in links:
  188. if js == False:
  189. temp_urls = find_by_url(link)
  190. else:
  191. temp_urls = find_by_url(link, js=True)
  192. if temp_urls == None: continue
  193. print(str(i) + " Find " + str(len(temp_urls)) + " URL in " + link)
  194. for temp_url in temp_urls:
  195. if temp_url not in urls:
  196. urls.append(temp_url)
  197. i -= 1
  198. return urls
  199. def giveresult(urls, domian):
  200. if urls == None:
  201. return None
  202. print("Find " + str(len(urls)) + " URL:")
  203. content_url = ""
  204. content_subdomain = ""
  205. for url in urls:
  206. content_url += url + "\n"
  207. print(url)
  208. subdomains = find_subdomain(urls, domian)
  209. print("\nFind " + str(len(subdomains)) + " Subdomain:")
  210. for subdomain in subdomains:
  211. content_subdomain += subdomain + "\n"
  212. print(subdomain)
  213. if args.outputurl != None:
  214. with open(args.outputurl, "a", encoding='utf-8') as fobject:
  215. fobject.write(content_url)
  216. print("\nOutput " + str(len(urls)) + " urls")
  217. print("Path:" + args.outputurl)
  218. if args.outputsubdomain != None:
  219. with open(args.outputsubdomain, "a", encoding='utf-8') as fobject:
  220. fobject.write(content_subdomain)
  221. print("\nOutput " + str(len(subdomains)) + " subdomains")
  222. print("Path:" + args.outputsubdomain)
  223. if __name__ == "__main__":
  224. urllib3.disable_warnings()
  225. args = parse_args()
  226. if args.file == None:
  227. if args.deep is not True:
  228. urls = find_by_url(args.url)
  229. giveresult(urls, args.url)
  230. else:
  231. urls = find_by_url_deep(args.url)
  232. giveresult(urls, args.url)
  233. else:
  234. if args.js is not True:
  235. urls = find_by_file(args.file)
  236. giveresult(urls, urls[0])
  237. else:
  238. urls = find_by_file(args.file, js = True)
  239. giveresult(urls, urls[0])

C#呼叫指令碼

  • 建立按鈕按下事件button1_Click,執行“呼叫python指令碼”函式runPythonSubdomain_js()
  1. private void button10_Click(object sender, EventArgs e)
  2. {
  3. richTextBox5.Clear();
  4. runPythonSubdomain_js();//執行python函式
  5. label24.Text = "開始掃描...";
  6. }
  • 例項化一個python程序 呼叫.py 指令碼
  1. void runPythonSubdomain_js()
  2. {
  3. string url = textBox9.Text;
  4. p = new Process();
  5. string path = "JSFinder.py";//待處理python檔案的路徑,本例中放在debug資料夾下
  6. string sArguments = path;
  7. ArrayList arrayList = new ArrayList();
  8. arrayList.Add("-u");
  9. arrayList.Add(url);//需要挖掘的域名
  10. foreach (var param in arrayList)//拼接引數
  11. {
  12. sArguments += " " + param;
  13. }
  14. p.StartInfo.FileName = @"D:\Anaconda\python.exe"; //沒有配環境變數的話,可以寫"xx\xx\python.exe"的絕對路徑。如果配了,直接寫"python"即可
  15. p.StartInfo.Arguments = sArguments;//python命令的引數
  16. p.StartInfo.UseShellExecute = false;
  17. p.StartInfo.RedirectStandardOutput = true;
  18. p.StartInfo.RedirectStandardInput = true;
  19. p.StartInfo.RedirectStandardError = true;
  20. p.StartInfo.CreateNoWindow = true;
  21. p.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
  22. p.Start();//啟動程序
  23. //MessageBox.Show("啟動成功");
  24. p.BeginOutputReadLine();
  25. p.OutputDataReceived += new DataReceivedEventHandler(p_OutputDataReceived_subdomain_js);
  26. Console.ReadLine();
  27. //p.WaitForExit();
  28. }
  29. void p_OutputDataReceived_subdomain_js(object sender, DataReceivedEventArgs e)
  30. {
  31. var printedStr = e.Data;
  32. Action at = new Action(delegate ()
  33. {
  34. //接受.py程序列印的字元資訊到文字顯示框
  35. richTextBox5.AppendText(printedStr + "\n");
  36. label24.Text = "掃描結束";
  37. });
  38. Invoke(at);
  39. }
  40. }
  41. }

三、使用步驟

  • 首先在url欄中輸入地址,接著可以選擇是使用ssl證書提取或者Js檔案提取的方式進行挖掘,最後得到子域名資訊。

github地址:https://github.com/Chenmengx/Penetration-testing-tool