Last active
April 4, 2024 12:28
-
-
Save karmendra/055ed683b5a8f8f5874423a2028f85aa to your computer and use it in GitHub Desktop.
This python3 script is to search for valid Visa Master or Amex card numbers in Linux file system, it is taken from https://github.com/ins1gn1a/CardScan4Linux but improved to automatically exclude binary files and verify card numbers using luhn algorithm, now --extention is optional, by default it includes all files
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
# Version: 1.1.0 | |
# Author: Adam Govier (ins1gn1a) - September 2015 | |
# Email: [email protected] | |
# | |
# Disclaimer: | |
# I am not responsible for any problems or issues that are potentially caused | |
# by running this tool. There should not be any problems as this script uses | |
# in-built functionality and was created with performance and availability | |
# in mind. Nevertheless, you've been told! | |
# Modules | |
import timeit | |
import re | |
import os | |
import sys | |
import argparse | |
import subprocess | |
from itertools import islice | |
# Colouring! | |
class bcolors: | |
HEADER = '\033[95m' | |
OKBLUE = '\033[94m' | |
OKGREEN = '\033[92m' | |
WARNING = '\033[93m' | |
FAIL = '\033[91m' | |
ENDC = '\033[0m' | |
BOLD = '\033[1m' | |
UNDERLINE = '\033[4m' | |
# Input argument setup | |
p = argparse.ArgumentParser(description='Search Linux-based systems for payment card numbers (VISA, AMEX, Mastercard).') | |
p.add_argument('-o','--output',dest='output',help='Output data to a file instead of the Terminal.',action='store_true') | |
p.add_argument('-D','--max-depth',dest='depth',help='Enter the max depth that the scanner will search from the given directory (Default is 3).',type=int,default=3) | |
p.add_argument('-d','--min-depth',dest='mindepth',help='Enter the min depth that the scanner will search from the given directory (No Default).',type=int) | |
p.add_argument('-l','--lines',dest='lines',help='Enter the number of lines from the file to cycle through (Default is 50)',type=int,default=50) | |
p.add_argument('-p','--path',help='Input the directory path that you want to recursively search through, e.g. /var (Default is /)',default='/') | |
p.add_argument('-e','--extensions',dest='extensions',help='Input the file extensions that should be searched for, separated by spaces.',required=False,nargs='+') | |
p.add_argument('-x','--exclude',dest='exclude_dir',help='Input the directories to exclude, separated by spaces. Wildcards can be used, e.g. /var/*',required=False,nargs='+',default="") | |
p.add_argument('-max','--max-size',help='Enter the maximum file-size to search for (Default 100 Kilobytes). Units: "c" for bytes, "k" for Kilobytes, "M" for Megabytes',dest="maxsize",default="100k") | |
p.add_argument('-min','--min-size',help='Enter the minimum file-size to search for (Default 16 Bytes). Units: "c" for bytes, "k" for Kilobytes, "M" for Megabytes',dest="minsize",default="16c") | |
p.add_argument('-mount','--scan-mount',dest='mounted',help='Enable to scan the mounted remote file systems (Default is off.)',required=False,action='store_true') | |
p.add_argument('-v','--verbose',dest='verbose',help='Display verbose messages (Warning: output can be huge).',required=False,action='store_true') | |
a = p.parse_args() | |
# Banner | |
print("----------------------------------------------------------------------------") | |
print(" ____ _ ____ _ _ _ _ ") | |
print(" / ___|__ _ _ __ __| / ___| ___ __ _ _ __ | || | | | (_)_ __ _ ___ __") | |
print("| | / _` | '__/ _` \___ \ / __/ _` | '_ \| || |_| | | | '_ \| | | \ \/ /") | |
print("| |__| (_| | | | (_| |___) | (_| (_| | | | |__ _| |___| | | | | |_| |> <") | |
print(" \____\__,_|_| \__,_|____/ \___\__,_|_| |_| |_| |_____|_|_| |_|\__,_/_/\_\ ") | |
print("----------------------------------------------------------- Version 1.1.0 --") | |
# String concatenation for file extension searching. | |
extCmd = "" | |
if a.extensions: | |
z = 0 | |
for ext in a.extensions: | |
if z == 0: | |
extCmd = " -name '*.%s'" %(ext) | |
z += 1 | |
else: | |
extCmd = extCmd + (" -o -name '*.%s'" %(ext)) | |
z += 1 | |
else: | |
extCmd = " -name '*'" | |
z = 1 | |
# Sizing | |
max = ("-size -" + a.maxsize) # Default 100 Kilobytes (100k) | |
min = ("-size +" + a.minsize) # Default 16 bytes (16 c) | |
# Exclude files via -x/--exclude | |
y = 0 | |
exclude_cmd = "" | |
for excl in a.exclude_dir: | |
if y == 0: | |
exclude_cmd = " ! -path '%s/*'" %(excl) | |
y += 1 | |
else: | |
exclude_cmd = exclude_cmd + (" -a ! -path '%s/*'" %(excl)) | |
y += 1 | |
if y > 0: | |
exclude_cmd = exclude_cmd + " " | |
header_exclusions = a.exclude_dir | |
else: | |
header_exclusions = "None" | |
# Output to stdout | |
if a.extensions and len(a.extensions) > 3: | |
header_line = "==============================================================================" | |
else: | |
header_line = "=========================================================" | |
print (bcolors.HEADER + header_line) | |
print (bcolors.HEADER + "[*]" + bcolors.ENDC + " Root Path \t\t" + bcolors.HEADER + ":\t" + bcolors.ENDC + str(a.path)) | |
print (bcolors.HEADER + "[*]" + bcolors.ENDC + " Max Size \t\t" + bcolors.HEADER + ":\t" + bcolors.ENDC + str(a.maxsize)) | |
print (bcolors.HEADER + "[*]" + bcolors.ENDC + " Min Size \t\t" + bcolors.HEADER + ":\t" + bcolors.ENDC + str(a.minsize)) | |
print (bcolors.HEADER + "[*]" + bcolors.ENDC + " Extensions \t\t" + bcolors.HEADER + ":\t" + bcolors.ENDC + str(a.extensions)) | |
print (bcolors.HEADER + "[*]" + bcolors.ENDC + " Lines per file \t" + bcolors.HEADER + ":\t" + bcolors.ENDC + str(a.lines)) | |
print (bcolors.HEADER + "[*]" + bcolors.ENDC + " Depth of search \t" + bcolors.HEADER + ":\t" + bcolors.ENDC + str(a.depth)) | |
print (bcolors.HEADER + "[*]" + bcolors.ENDC + " Scan Mounted Dirs \t" + bcolors.HEADER + ":\t" + bcolors.ENDC + str(a.mounted)) | |
print (bcolors.HEADER + "[*]" + bcolors.ENDC + " Exclusions \t\t" + bcolors.HEADER + ":\t" + bcolors.ENDC + str(header_exclusions)) | |
print (bcolors.HEADER + header_line + bcolors.ENDC) | |
print (bcolors.OKGREEN + "\n[*] " + bcolors.ENDC + "Starting file-system scan. This may take a while...") | |
start_time = timeit.default_timer() | |
# Local or Remote Mounting | |
if a.mounted: | |
remote_mount = "" | |
else: | |
remote_mount = "-mount " | |
# Min depth | |
if a.mindepth is None: | |
min_depth = "" | |
else: | |
min_depth = "-mindepth %s " %(str(a.mindepth)) | |
# Create a list of all files with the provided inputs | |
try: | |
findCmd='find %s %s-maxdepth %s %s-type f \( %s %s\) %s %s -exec sh -c \'file -bi "$1" | grep -qv "charset=binary"\' _ {} \; -print' %(a.path,remote_mount,a.depth,min_depth,extCmd,exclude_cmd,max,min) | |
if a.verbose: | |
print(findCmd) | |
full_path_list = subprocess.check_output(findCmd, shell=True) | |
full_path_list = full_path_list.decode().rstrip().split('\n') | |
except Exception as e: | |
print('Exception:- %s', e) | |
sys.exit(bcolors.FAIL + "[*] " + bcolors.ENDC + "Cannot retrieve file list - Likely too many symbolic links.") | |
# Count how many entries in the list file | |
file_lines = len(full_path_list) | |
# Output to user | |
print (bcolors.OKGREEN + "[*] " + bcolors.ENDC + "File-system search complete. " + str(file_lines) + " files to check for card-data.") | |
# Regex to filter card numbers | |
regexAmex = re.compile("([^0-9-]|^)(3(4[0-9]{2}|7[0-9]{2})( |-|)[0-9]{6}( |-|)[0-9]{5})([^0-9-]|$)") #16 Digit AMEX | |
regexVisa = re.compile("([^0-9-]|^)(4[0-9]{3}( |-|)([0-9]{4})( |-|)([0-9]{4})( |-|)([0-9]{4}))([^0-9-]|$)") | |
regexMaster = re.compile("([^0-9-]|^)(5[0-9]{3}( |-|)([0-9]{4})( |-|)([0-9]{4})( |-|)([0-9]{4}))([^0-9-]|$)") | |
# Function to check if the extracted number is a valid Luhn number | |
def is_luhn_valid(card_number): | |
number = card_number | |
card_number = [int(num) for num in card_number if num.isdigit()] | |
checkDigit = card_number.pop(-1) | |
card_number.reverse() | |
card_number = [num * 2 if idx % 2 == 0 | |
else num for idx, num in enumerate(card_number)] | |
card_number = [num - 9 if idx % 2 == 0 and num > 9 | |
else num for idx, num in enumerate(card_number)] | |
card_number.append(checkDigit) | |
checkSum = sum(card_number) | |
is_valid = checkSum % 10 == 0 | |
#print("Card: %s is %s" % (number, is_valid)) | |
return is_valid | |
# Log file - counting | |
total_count = 0 | |
# Search through files in the list | |
try: | |
for filepath in full_path_list: | |
filepath = filepath.rstrip('\n') | |
try: | |
with open(filepath) as file: | |
if a.verbose: | |
print(filepath) | |
total_count += 1 | |
with open('/tmp/cardscan4linux.log', 'w') as log_file: | |
log_file.write(str(file_lines) + "/" + str(total_count) + "\n") | |
i = 0 | |
results = [] | |
head = list(islice(file, a.lines)) # Opens 50 lines by default | |
# Loops through each item in list | |
for item in head: | |
amex = re.search(regexAmex, item.rstrip('\n')) | |
visa = re.search(regexVisa, item.rstrip('\n')) | |
master = re.search(regexMaster, item.rstrip('\n')) | |
# Prints if matches AMEX | |
if amex and is_luhn_valid(amex.group().replace(',', '').strip()): | |
i += 1 | |
results.append("\tAMEX:\t\t " + bcolors.FAIL + amex.group().replace(',','').strip() + bcolors.ENDC) | |
# Prints if matches VISA | |
#elif visa: | |
elif visa and is_luhn_valid(visa.group().replace(',', '').strip()): | |
i += 1 | |
results.append("\tVISA:\t\t " + bcolors.FAIL + visa.group().replace(',','').strip() + bcolors.ENDC) | |
# Prints if matches Mastercard | |
#elif master: | |
elif master and is_luhn_valid(master.group().replace(',', '').strip()): | |
i += 1 | |
results.append("\tMASTERCARD:\t " + bcolors.FAIL + master.group().replace(',','').strip() + bcolors.ENDC) | |
if i > 0: | |
if a.output: | |
with open('cardscan.output', "a") as outfile: | |
outfile.write("File: " + filepath + "\n") | |
for result in results: | |
outfile.write(result + "\n") | |
else: | |
print("\nFile: " + filepath) | |
for result in results: | |
print(result) | |
except FileNotFoundError as e: | |
if a.verbose: | |
print("File not found:", str(e)) | |
except UnicodeDecodeError as e: | |
if a.verbose: | |
print("Unicode decode error:", str(e), " for file: ", filepath) | |
except KeyboardInterrupt: | |
break | |
except Exception as e: | |
print("An exception occurred:", str(e)) | |
with open('cardscan4linux-error.log','a') as errlog: | |
errlog.write("File: " + filepath + "\n" + e + "\n") | |
sys.exit(bcolors.FAIL + "[*] " + bcolors.ENDC + "Cannot open file '" + filepath + "'.") | |
except Exception as e: | |
sys.exit(bcolors.WARNING + "\r[*] " + bcolors.ENDC + "There are no files that match the search.") | |
# Removes the temp file | |
try: | |
os.remove("/tmp/cardscan4linux.log") | |
except OSError: | |
pass | |
total_time = int(timeit.default_timer()) - int(start_time) | |
# End of file | |
print(bcolors.OKGREEN + "[*] " + bcolors.ENDC + "Card scanning complete. " + str(file_lines) + " total files were scanned in " + str(total_time) + " seconds.") | |
if a.output: | |
print(bcolors.OKGREEN + "[*] " + bcolors.ENDC + "Output saved to " + (os.path.dirname(os.path.realpath(__file__))) + "/cardscan.output.") | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment