import os, pwd, re, pandas as pd, requests #apt install python3-pandas python3-requests python3-socks def main(): rootpath='/srv/darknet-onion-webring/' urlpath=pwd.getpwuid(os.getuid()).pw_dir+"/.darknet_participant_url" #print(urlpath) # check if ~/.darknet_participant_url exists, # if exists, instance= the content of ~/.darknet_participant_url (which is the url: such as uptime.nowherejez...onion) isitvalid="n" while isitvalid != "y": if os.path.isfile(urlpath): with open(urlpath) as f: instance = f.read().rstrip() # check if the instance URL domain is valid #print(urlpath,instance) if IsOnionValid(instance): print("[+] Instance Name:",instance,IsOnionValid(instance)) else: print('[-] Invalid instance name in ~/.darknet_participant_url:', instance) return False instancepath=rootpath+'www/participants/'+instance verifiedcsvfile=instancepath+'/verified.csv' vdf = pd.read_csv(verifiedcsvfile) unverifiedcsvfile=instancepath+'/unverified.csv' uvdf = pd.read_csv(unverifiedcsvfile) #df = pd.read_csv(csvfile) print("[+] file exists, your Webring URL is", instance) isitvalid = "y" else: print("[+] Instance Path doesn't exist yet") # and ask for the instance URL domain instance = input("What is your Instance domain ? (ex: uptime.nowherejezfoltodf4jiyl6r56jnzintap5vyjlia7fkirfsnfizflqd.onion):") instancepath=rootpath+'www/participants/'+instance # check if the instance URL domain is valid if IsOnionValid(instance): print("[+] Instance Name: ",instance,IsUrlValid(instance)) else: print('[-] Invalid instance name in ~/.darknet_participant_url:', instance) return False # ask the user if the instance URL is valid ? print() print(instance) isitvalid=input("Is this your this your instance domain ? (y/n)") # if yes, then write it into ~/.darknet_participant_url if isitvalid == "y" : print("OK writing the instance url to ~/.darknet_participants_url") with open(urlpath, "w") as file: file.write(instance) print("[+] file written, let's read it") f = open(urlpath,"r") print(f.read()) print("[+] Initial Setup Completed!") while True: print("[+] Welcome to the Darknet Onion Webring, where you are exploring the Darknet and helping others do the same.") print(""" Managing Websites: 1) Add a new Website entry (into unverified.csv) 2) Trust a Website entry (move an entry from unverified to verified.csv) 3) Untrust a Website entry (move an entry from unverified to verified.csv) Managing Webring Participants: 4) Add a new webring participant (and download their files into their directory (without trusting them yet!)) 5) Trust a webring participant (Potentially dangerous) 6) Untrust a webring participant 7) Remove a webring participant Managing Wordlists: 8) Add/Remove words or links in the sensitive list (ex: drug) 9) Add/Remove words or links in the blacklist (ex: porn) 0) Exit """) option = input("Select Option? (0-6): ") print(option) match option: ########## MANAGING WEBSITE ENTRIES ################# #Websites: # 1) Add a new Website entry (into unverified.csv) # 2) Trust a Website entry (move an entry from unverified to verified.csv) # 3) Untrust a Website entry (move an entry from unverified to verified.csv) ##################################################### case "1": print("\n[+] Add a new Website entry (into unverified.csv)") name='' while(IsNameValid(name) is not True): name = input("What is the Website name ? ") category='' while(IsCategoryValid(category) is not True): category = input("What is the website Category ? ") # the url of the website (required) + check if its valid #entry_url = input("What is URL of the Website ? (ex: https://torproject.org or http://2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion)") url='' while(IsUrlValid(url) is not True): url=input("What is the website URL ? ") # a quick description (optional) + check if its valid #entry_desc = input("(Optional) Description of the website ? (max 256 characters) (press enter to skip)") #desc="This is a new website that we add, it has this description" desc='DEFAULT' while(IsDescriptionValid(desc) is not True): desc=input("Description for the website ? (Optional)") # sensitive ? (y/n) + check if its valid #entry_sensi = input("is it a sensitive website ? (ex: website related to drugs) (y/n)") sensi = '' while(IsStatusValid(sensi) is not True): sensi=input("Is this website sensitive (ex: related to drugs) ? (y/n)") newrow=[instance,category,name,url,sensi,desc,'',''] print("[+] NEWROW=",newrow) # (rest is automatic: status, score, instance is = '' because it is your own instance) # check if the entry doesn't already exist in verified.csv and in unverified.csv # if it doesnt exist, add it into unverified.csv uvdf.loc[-1] = newrow # adding a row uvdf.index = uvdf.index + 1 # shifting index uvdf = uvdf.sort_index() # sorting by index print("[+] New row added! now writing the csv file:") uvdf.to_csv(unverifiedcsvfile, index=False) case "2": print("[+] Trust a Website entry (move an entry from unverified to verified.csv)") # search for a word name='' while(IsNameValid(name) is not True): name = input("What is the Website name you want to trust ? (ex: Nowhere)") filter_uvdf = uvdf[uvdf.Name.str.contains(name)] # and display only the matching entries in unverified.csv in an array format (display it in CLI). print(filter_uvdf[['Name','URL']]) # check if there are no results, dont proceed if there are none! if filter_uvdf.size == 0: print("ERROR no results, skipping.") else: # Each of the rows has an index, index=-1 while (index not in filter_uvdf.index): # prompt the user to ask for with row they want to move to verified.csv index = int(input("What is the index of the entry that you want to move to verified.csv ? (ex: 3) ")) # once selected, it must be able to SAVE and print that row: print(uvdf.iloc[index].values) newrow=uvdf.iloc[index].values # append it into verified.csv vdf.loc[-1] = newrow # adding a row vdf.index = vdf.index + 1 # shifting index vdf = vdf.sort_index() # sorting by index vdf.to_csv(verifiedcsvfile, index=False) print("[+] New row added to verified.csv! now writing to the csv") # remove it from unverified.csv uvdf.drop(index, inplace= True) uvdf.to_csv(unverifiedcsvfile, index=False) print("[+] Link is now moved to verified.csv!") case "3": print("[+] Untrust a Website entry (move an entry from verified to unverified.csv)") # search for a word name='' while(IsNameValid(name) is not True): name = input("What is the Website name you want to untrust ? (ex: BreachForums)") filter_vdf = vdf[vdf.Name.str.contains(name)] # and display only the matching entries in unverified.csv in an array format (display it in CLI). print(filter_vdf[['Name','URL']]) # check if there are no results, dont proceed if there are none! if filter_vdf.size == 0: print("ERROR no results, skipping.") else: # Each of the rows has an index, index=-1 while (index not in filter_vdf.index): # prompt the user to ask for with row they want to move to unverified.csv index = int(input("What is the index of the entry that you want to move to unverified.csv ? (ex: 3) ")) # once selected, it must be able to SAVE and print that row: print(vdf.iloc[index].values) newrow=vdf.iloc[index].values # append it into unverified.csv uvdf.loc[-1] = newrow # adding a row uvdf.index = uvdf.index + 1 # shifting index uvdf = uvdf.sort_index() # sorting by index uvdf.to_csv(unverifiedcsvfile, index=False) print("[+] New row added to unverified.csv!") # remove it from verified.csv vdf.drop(index, inplace= True) vdf.to_csv(verifiedcsvfile, index=False) print("[+] Link is now moved to unverified.csv!") ####### MANAGING WEBRING PARTICIPANTS ########### # 4) Add a new webring participant (and download their files into their directory (without trusting them yet!)) # 5) Trust a webring participant (Potentially dangerous) # 6) Untrust a webring participant # 7) Remove a webring participant ##################################################### case "4": print("[+] Add a new webring participant (and download their files into their directory (without trusting them yet!))") webring_participant_url = '' while(IsOnionValid(webring_participant_url) is not True): # ask for the url to the other webring participant and check if the (onion only) url is valid or not: webring_participant_url = input("What is the onion domain of the new webring participant? (ex: uptime.nowherejezfoltodf4jiyl6r56jnzintap5vyjlia7fkirfsnfizflqd.onion) ") # check if the directory exists locally or not, participantdir=rootpath+'www/participants/'+webring_participant_url if os.path.isdir(participantdir): # if it does, it means that the webring is ALREADY added print("[-] Webring Participant is already listed, skipping.") return False else: # if not, then proceed: # print the URL to the csv files at http://URL.onion/participants/URL.onion/{verified.csv,unverified.csv,sensitive.csv,blacklist.csv,webring-participants.csv} basewurl='http://'+webring_participant_url+'/participants/'+webring_participant_url+'/' print(basewurl) print('[+] Checking if all of the required csv files exists for new webring participant ',webring_participant_url, ": ") w_vcsv=basewurl+'verified.csv' w_uvcsv=basewurl+'unverified.csv' #print(CheckUrl(w_uvcsv)) w_blcsv=basewurl+'blacklist.csv' #print(CheckUrl(w_blcsv)) w_scsv=basewurl+'sensitive.csv' #print(CheckUrl(w_scsv)) w_webcsv=basewurl+'webring-participants.csv' #print(CheckUrl(w_webcsv)) if CheckUrl(w_vcsv) is False or CheckUrl(w_uvcsv) is False or CheckUrl(w_blcsv) is False or CheckUrl(w_scsv) is False or CheckUrl(w_webcsv) is False: print("[-] Webring Participant is invalid, exiting.") return False else: print("[+] Webring Participant is valid, adding it.") # verify that their verified.csv csv file exists at basewurl+'verified.csv' # then download their csv files at http://URL.onion/participants/URL.onion/{verified.csv,unverified.csv,sensitive.csv,blacklist.csv,webring-participants.csv} # and remove all of the invalid entries !!! # TODO if OK then add it to the webring-participants.csv file ####################################################################### #newrow=[instance,category,name,url,sensi,desc,'',''] #print("[+] NEWROW=",newrow) #uvdf.loc[-1] = newrow # adding a row #uvdf.index = uvdf.index + 1 # shifting index #uvdf = uvdf.sort_index() # sorting by index #print("[+] New row added! now writing the csv file:") #uvdf.to_csv(unverifiedcsvfile, index=False) case "5": print("[+] Trust a webring participant (Potentially dangerous)") # TODO list the existing webring participants from webring-participants.csv and # TODO ask the user to pick the index (0-9?) of the instance to trust # TODO if index is valid, then mark the instance as trusted in webring-participants.csv searchterm = input("What is the index of the entry that you want to trust ?") case "6": # TODO print("[+] Untrust a webring participant") case "7": # TODO print("[+] Remove a webring participant") ################### MANAGING WORDLISTS ################# #Wordlists: # 8) Add/Remove words in the sensitive list (ex: drug) # 9) Add/Remove words in the blacklist (ex: porn) # 0) Exit ######################################################### case "8": print("[+] Add/Remove words in the sensitive list (ex: drug)") print("do you want to 1) add words or 2) remove words ?") option="0" done = False while(done == False): while option != "1" and option != "2" and option != "exit": option=input("do you want to 1) add words or 2) remove words ? (type exit to exit)") # TODO display the contents of sensitive.csv file if option == 1: word=input("which word do you want to add? (write 0 to exit") if word == "0": done = True #True to get out of the while loop else: print("checking if word is valid") # TODO check if word is valid in a while loop (dont check if # TODO if invalid! remove word at index else: index=input("which word do you want to remove? (index 0 to (max index) (write exit to exit)") if index == "exit": done = True #True to get out of the while loop else: print("checking if index is valid") # TODO check if index is valid or not # TODO if valid! remove word at index # TODO if invalid! just pass to ask for another word case "9": print("[+] Add/Remove words in the blacklist list (ex: porn)") # TODO copy option 5 # TODO print("do you want to 1) add words or 2) remove words ?") # TODO display the contents of blacklist.csv file case _: print("[-] Exiting") return True #### Checking Functions to validate that links are legit #### def CheckUrl(url): """ Checks if URL is actually reachable via Tor """ proxies = { 'http': 'socks5h://127.0.0.1:9050', 'https': 'socks5h://127.0.0.1:9050' } try: status = requests.get(url,proxies=proxies, timeout=5).status_code print('[+]',url,status) if status != 502: print(url,"✔️") return True else: print(url,"❌") return False except requests.ConnectionError as e: print(url,"❌") return False except requests.exceptions.ReadTimeout as e: print(url,"❌") return False #### PROTECTIONS AGAINST MALICIOUS CSV INPUTS #### def IsOnionValid(url: str)-> bool: """ Checks if the domain(param) is a valid onion domain and return True else False. """ # check if the characters are only [a-zA-Z0-9.] with maximum 128 chars max? # check that it is only url.onion or subdomain.url.onion, # if OK return True #if not : return False try: pattern = re.compile("^[A-Za-z0-9.]+(\.onion)?$") url = url.strip().removesuffix('/') if url.startswith('http://'): print('URL starts with http') # Removes the http:// domain = url.split('/')[2] if pattern.fullmatch(domain) is not None: if len(domain.split('.')) > 3: n_subdomians = len(domain.split('.')) # Checks if there is more than 1 subdomain. "subdomain.url.onion" only print(f"This domain have more than one subdomain. There are {n_subdomians} subdomains") return False else: if len(domain) < 62: print("Domain length is less than 62.") return False return True elif pattern.fullmatch(domain) is None: print("Domain contains invalid character.") print(domain) return False else: print("Domain not valid") return False else: #print("URL doesn't start http") if pattern.fullmatch(url) is not None: if len(url.split('.')) > 3: n_subdomians = len(url.split('.')) # Checks if there is more than 1 subdomain. "subdomain.url.onion" only print(f"This domain have more than one subdomain. There are {n_subdomians - 1} subdomains") return False else: if len(url) < 62: print("Domain length is less than 62.") return False return True elif pattern.fullmatch(url) is None: print("Domain contains invalid character.") print(url) return False else: print("Domain not valid") return False except Exception as e: print(f"Error: {e}") def IsUrlValid(url:str)->bool: """ Check if url is valid both dark net end clearnet. """ # check if the characters are only [a-zA-Z0-9.:/] with maximum 128 chars max? # check that it is only http(s)://wordA.wordB or http(s)://WordC.WordB.WordC, (onion or not), clearnet is fine too (double check if those are fine!) # if OK return True #if not : return False pattern = re.compile("^[A-Za-z0-9:/.]+$") if url.endswith('.onion'): return IsOnionValid(url) else: if not url.__contains__('.'): print("No (DOT) in clearnet url") return False if pattern.fullmatch(url) is None: print('Url contains invalid chars') return False return True def IsStatusValid(status: str)-> bool: """ Checks if status contains only [v,w]. Verbose only if False is returned """ # check if the characters are only [vx] with maximum 1 chars max # if OK return True #if not : return False pattern = ['y','n'] if len(status) != 1: print("Got more than one character or nothing.") return False elif (status not in pattern): print("Got an invalid character it must be either y or n") return False return True def IsDescriptionValid(desc:str)->bool: """ Check the categories are only [a-zA-Z0-9,.' ] with 256 max chars. """ # check if the characters are only [a-zA-Z0-9.,' ] with maximum 256 chars max #(careful with the ' and , make sure you test if it fucks the csv up or else) # if OK return True #if not : return False pattern = re.compile("^[A-Za-z0-9-.,' ]+$") desc.strip() # empty description is fine as it's optional if desc == "DEFAULT": return False elif len(desc) > 256: print("desc is greater than 256 chars") return False return True def IsCategoryValid(categories: list)-> bool: """ Check the categories are only [a-zA-Z0-9 ] with 64 max chars. """ # check if the characters are only [a-zA-Z0-9 ] with maximum 64 chars max #(careful with the ' and , make sure you test if it fucks the csv up or else) # if OK return True #if not : return False pattern = re.compile("^[A-Za-z0-9 ]+$") for category in categories: category.strip() if pattern.fullmatch(category) is None: #print('Got an empty list or invalid chars') return False elif len(category) > 64: print('Category is too long') else: return True def IsNameValid(name: str)->bool: """ Check the parameter name only contains [a-zA-Z0-9 ] and is 64 chars long. """ # check if the characters are only [a-zA-Z0-9 ] with maximum 64 chars max #(careful with the ' and , make sure you test if it fucks the csv up or else) # if OK return True #if not : return False pattern = re.compile("^[A-Za-z0-9 ]+$") name = name.strip() if (pattern.fullmatch(name) is None): #print("Got an invalid character or nothing") return False elif len(name) > 64: print(f'Got a name lenght greater than 64. {len(name)}') return False return True if __name__ == '__main__': main()