├── .gitignore ├── LICENSE ├── README.md └── cardscan4linux.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .coverage.* 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | *,cover 45 | 46 | # Translations 47 | *.mo 48 | *.pot 49 | 50 | # Django stuff: 51 | *.log 52 | 53 | # Sphinx documentation 54 | docs/_build/ 55 | 56 | # PyBuilder 57 | target/ 58 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CardScan4Linux 2 | ![](https://img.shields.io/maintenance/yes/2015.svg) 3 | 4 | This script can be used to locally search through stored files for any Credit/Debit card details. It is portable and requires no additional Python (built with 2.7 in mind) libraries to operate. 5 | 6 | ## Basic Usage 7 | `cardscan4linux.py [-h] [-o] [-D DEPTH] [-d MINDEPTH] [-l LINES] [-p PATH] -e EXTENSIONS [EXTENSIONS ...] [-x EXCLUDE_DIR [EXCLUDE_DIR ...]] [-max MAXSIZE] [-min MINSIZE] [-mount] [-v]` 8 | 9 | ## Scan Depth 10 | The `-d` and `-D` command flags are used to specify the minimum scan depth, and also the maximum scan depth. This is useful for instances where too many symlinked directories result in `find` errors. 11 | 12 | * Default Min: 0 13 | * Default Max: 3 14 | 15 | ## Remote Scanning via Mounting 16 | By mounting a remote file system to the local (i.e. where the script will be run) Linux system you can effectively scan the remote host by using the `-mount` command flag when running the tool. By default remote mounted systems are not scanned. 17 | 18 | * Default: False (Off) 19 | 20 | ## Excluding Directories 21 | It is possible to exclude certain directories from being scanned by using the `-x/--exclude` command flag when running the script. Multiple directories can be excluded, which includes the use of wildcards using the asterisk character `*`. An example is as follows: `-x /var */adam/* /tmp`. 22 | 23 | Note: It is not neccessary to include wildcards, however if you are using a child-directory as the exclusions then the wildcards will be necessary either side of the forward slashes. 24 | 25 | * Default: NONE 26 | 27 | ## Min Size / Max Size 28 | The `-min/--max-size` and `-max/--max-size` command flags are used when performing the file discovery. Specifically each are used to set the minimum and maximum file sizes, respectively, of the files that will be audited for payment card-data. 29 | 30 | Sizes are denoted within 'bytes', c = bytes, k = Kilobytes, M = Megabytes, G = Gigabytes 31 | 32 | * Default Min: 16c 33 | * Default Max: 100k 34 | 35 | ## Extensions 36 | To specify the targeted file-extensions use the `-e/--extensions` command flag, with one or more extension types separated by spaces. For example: `-e txt doc xlsx csv`. There is no limit to the amount of extensions that can be used for the search, however bear in mind that the more extensions that are specified then the longer the scan could possibly take to complete. Be weary whilst scaning file-storage servers. 37 | 38 | * Default: NONE 39 | 40 | ## Max Number of Lines to Audit 41 | By default the maximum number of lines that will be audited within each file will be '50'. Specifying more will perform a more thorough scan for card data, but more resources will be used and the total scan time will increase. Example: `-l 250` 42 | 43 | * Default: 50 44 | 45 | ## Optional Arguments: 46 | `-h, --help` Show this help message and exit 47 |
`-o, --output ` Output data to a file instead of the Terminal. 48 |
`-D DEPTH, --max-depth DEPTH` Enter the max depth that the scanner will search from the given directory (Default is 3). 49 |
`-d MINDEPTH, --min-depth MINDEPTH` Enter the min depth that the scanner will search from the given directory (No Default). 50 |
`-l LINES, --lines LINES` Enter the number of lines from the file to cycle through (Default is 50) 51 |
`-p PATH, --path PATH` Input the directory path that you want to recursively search through, e.g. /var (Default is /) 52 |
`-e EXTENSIONS [EXTENSIONS ...], --extensions EXTENSIONS [EXTENSIONS ...]` Input the file extensions that should be searched for, separated by spaces. 53 |
`-x EXCLUDE_DIR [EXCLUDE_DIR ...], --exclude EXCLUDE_DIR [EXCLUDE_DIR ...]` Input the directories to exclude, separated by spaces. Wildcards can be used, e.g. /var/* 54 |
`-max MAXSIZE, --max-size MAXSIZE` Enter the maximum file-size to search for (Default 100 Kilobytes). Units: "c" for bytes, "k" for Kilobytes, "M" for Megabytes 55 |
`-min MINSIZE, --min-size MINSIZE` Enter the minimum file-size to search for (Default 16 Bytes). Units: "c" for bytes, "k" for Kilobytes, "M" for Megabytes 56 |
`-mount, --scan-mount` Enable to scan the mounted remote file systems (Default is off.) 57 |
`-v, --verbose` Display verbose messages (Warning: output can be huge). 58 | 59 | ## Example Output 60 | `[root@sc ~]# ./cardscan4linux.py -e txt -d 8` 61 |
`===================================` 62 |
`[ Root Path ]______________/` 63 |
`[ Max Size ]_______________100k` 64 |
`[ Min Size ]_______________16c` 65 |
`[ Extensions ]_____________['txt']` 66 |
`[ Lines per file ]_________50` 67 |
`[ Depth of search ]________8` 68 |
`[ Scan Mounted Dirs ]______False` 69 |
`[ Exclusions ]_____________/var` 70 |
`===================================` 71 | 72 |
`[*] Starting file-system scan. This may take a while...` 73 |
`[*] File-system search complete. 24855 files to check for card-data.` 74 |
`File: /home/carddetails.txt` 75 |
` AMEX: 371449635398431` 76 |
` AMEX: 371427352388125` 77 |
`File: /root/test.txt` 78 |
` AMEX: 378282246310005` 79 |
` AMEX: 371449635398431` 80 |
` AMEX: 378734493671000` 81 |
` MASTERCARD: 5105105105105100` 82 |
` VISA: 4111111111111111` 83 |
` VISA: 4012888888881881` 84 |
` MASTERCARD: 5522131028402823` 85 | 86 | `[*] Card scanning complete. 24855 total files were scanned in 8 seconds.` 87 | 88 | # To do 89 | 90 | * Create some sort of progress bar for the 'find' subprocess 91 | * Party 92 | -------------------------------------------------------------------------------- /cardscan4linux.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Version: 1.1.0 4 | # Author: Adam Govier (ins1gn1a) - September 2015 5 | # Email: me@ins1gn1a.com 6 | # 7 | # Disclaimer: 8 | # I am not responsible for any problems or issues that are potentially caused 9 | # by running this tool. There should not be any problems as this script uses 10 | # in-built functionality and was created with performance and availability 11 | # in mind. Nevertheless, you've been told! 12 | 13 | # Modules 14 | import timeit 15 | import re 16 | import os 17 | import sys 18 | import argparse 19 | import subprocess 20 | from itertools import islice 21 | 22 | # Colouring! 23 | class bcolors: 24 | HEADER = '\033[95m' 25 | OKBLUE = '\033[94m' 26 | OKGREEN = '\033[92m' 27 | WARNING = '\033[93m' 28 | FAIL = '\033[91m' 29 | ENDC = '\033[0m' 30 | BOLD = '\033[1m' 31 | UNDERLINE = '\033[4m' 32 | 33 | # Input argument setup 34 | p = argparse.ArgumentParser(description='Search Linux-based systems for payment card numbers (VISA, AMEX, Mastercard).') 35 | p.add_argument('-o','--output',dest='output',help='Output data to a file instead of the Terminal.',action='store_true') 36 | 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) 37 | 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) 38 | 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) 39 | p.add_argument('-p','--path',help='Input the directory path that you want to recursively search through, e.g. /var (Default is /)',default='/') 40 | p.add_argument('-e','--extensions',dest='extensions',help='Input the file extensions that should be searched for, separated by spaces.',required=True,nargs='+') 41 | 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="") 42 | 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") 43 | 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") 44 | 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') 45 | p.add_argument('-v','--verbose',dest='verbose',help='Display verbose messages (Warning: output can be huge).',required=False,action='store_true') 46 | a = p.parse_args() 47 | 48 | # Banner 49 | print "----------------------------------------------------------------------------" 50 | print " ____ _ ____ _ _ _ _ " 51 | print " / ___|__ _ _ __ __| / ___| ___ __ _ _ __ | || | | | (_)_ __ _ ___ __" 52 | print "| | / _` | '__/ _` \___ \ / __/ _` | '_ \| || |_| | | | '_ \| | | \ \/ /" 53 | print "| |__| (_| | | | (_| |___) | (_| (_| | | | |__ _| |___| | | | | |_| |> <" 54 | print " \____\__,_|_| \__,_|____/ \___\__,_|_| |_| |_| |_____|_|_| |_|\__,_/_/\_\ " 55 | print "----------------------------------------------------------- Version 1.1.0 --" 56 | 57 | # String concatenation for file extension searching. 58 | extCmd = "" 59 | z = 0 60 | for ext in a.extensions: 61 | if z == 0: 62 | extCmd = " -name '*.%s'" %(ext) 63 | z += 1 64 | else: 65 | extCmd = extCmd + (" -o -name '*.%s'" %(ext)) 66 | z += 1 67 | 68 | # Sizing 69 | max = ("-size -" + a.maxsize) # Default 100 Kilobytes (100k) 70 | min = ("-size +" + a.minsize) # Default 16 bytes (16 c) 71 | 72 | # Exclude files via -x/--exclude 73 | y = 0 74 | exclude_cmd = "" 75 | for excl in a.exclude_dir: 76 | if y == 0: 77 | exclude_cmd = " ! -path '%s/*'" %(excl) 78 | y += 1 79 | else: 80 | exclude_cmd = exclude_cmd + (" -a ! -path '%s/*'" %(excl)) 81 | y += 1 82 | 83 | if y > 0: 84 | exclude_cmd = exclude_cmd + " " 85 | header_exclusions = a.exclude_dir 86 | else: 87 | header_exclusions = "None" 88 | 89 | # Output to stdout 90 | if len(a.extensions) > 3: 91 | header_line = "==============================================================================" 92 | else: 93 | header_line = "=========================================================" 94 | print (bcolors.HEADER + header_line) 95 | print (bcolors.HEADER + "[*]" + bcolors.ENDC + " Root Path \t\t" + bcolors.HEADER + ":\t" + bcolors.ENDC + str(a.path)) 96 | print (bcolors.HEADER + "[*]" + bcolors.ENDC + " Max Size \t\t" + bcolors.HEADER + ":\t" + bcolors.ENDC + str(a.maxsize)) 97 | print (bcolors.HEADER + "[*]" + bcolors.ENDC + " Min Size \t\t" + bcolors.HEADER + ":\t" + bcolors.ENDC + str(a.minsize)) 98 | print (bcolors.HEADER + "[*]" + bcolors.ENDC + " Extensions \t\t" + bcolors.HEADER + ":\t" + bcolors.ENDC + str(a.extensions)) 99 | print (bcolors.HEADER + "[*]" + bcolors.ENDC + " Lines per file \t" + bcolors.HEADER + ":\t" + bcolors.ENDC + str(a.lines)) 100 | print (bcolors.HEADER + "[*]" + bcolors.ENDC + " Depth of search \t" + bcolors.HEADER + ":\t" + bcolors.ENDC + str(a.depth)) 101 | print (bcolors.HEADER + "[*]" + bcolors.ENDC + " Scan Mounted Dirs \t" + bcolors.HEADER + ":\t" + bcolors.ENDC + str(a.mounted)) 102 | print (bcolors.HEADER + "[*]" + bcolors.ENDC + " Exclusions \t\t" + bcolors.HEADER + ":\t" + bcolors.ENDC + str(header_exclusions)) 103 | print (bcolors.HEADER + header_line + bcolors.ENDC) 104 | print (bcolors.OKGREEN + "\n[*] " + bcolors.ENDC + "Starting file-system scan. This may take a while...") 105 | start_time = timeit.default_timer() 106 | 107 | # Local or Remote Mounting 108 | if a.mounted: 109 | remote_mount = "" 110 | else: 111 | remote_mount = "-mount " 112 | 113 | # Min depth 114 | if a.mindepth is None: 115 | min_depth = "" 116 | else: 117 | min_depth = "-mindepth %s " %(str(a.mindepth)) 118 | 119 | # Create a list of all files with the provided inputs 120 | try: 121 | full_path_list = subprocess.check_output('find %s %s-maxdepth %s %s-type f \( %s %s\) %s %s ' %(a.path,remote_mount,a.depth,min_depth,extCmd,exclude_cmd,max,min), shell=True) 122 | full_path_list = full_path_list.rstrip().split('\n') 123 | except: 124 | sys.exit(bcolors.FAIL + "[*] " + bcolors.ENDC + "Cannot retrieve file list - Likely too many symbolic links.") 125 | 126 | # Count how many entries in the list file 127 | file_lines = len(full_path_list) 128 | 129 | # Output to user 130 | print (bcolors.OKGREEN + "[*] " + bcolors.ENDC + "File-system search complete. " + str(file_lines) + " files to check for card-data.") 131 | 132 | # Regex to filter card numbers 133 | 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 134 | regexVisa = re.compile("([^0-9-]|^)(4[0-9]{3}( |-|)([0-9]{4})( |-|)([0-9]{4})( |-|)([0-9]{4}))([^0-9-]|$)") 135 | regexMaster = re.compile("([^0-9-]|^)(5[0-9]{3}( |-|)([0-9]{4})( |-|)([0-9]{4})( |-|)([0-9]{4}))([^0-9-]|$)") 136 | 137 | # Log file - counting 138 | total_count = 0 139 | 140 | # Search through files in the list 141 | try: 142 | for filepath in full_path_list: 143 | filepath = filepath.rstrip('\n') 144 | try: 145 | with open(filepath) as file: 146 | if a.verbose: 147 | print filepath 148 | total_count += 1 149 | with open('/tmp/cardscan4linux.log', 'w') as log_file: 150 | log_file.write(str(file_lines) + "/" + str(total_count) + "\n") 151 | 152 | i = 0 153 | results = [] 154 | head = list(islice(file, a.lines)) # Opens 50 lines by default 155 | 156 | # Loops through each item in list 157 | for item in head: 158 | amex = re.search(regexAmex, item.rstrip('\n')) 159 | visa = re.search(regexVisa, item.rstrip('\n')) 160 | master = re.search(regexMaster, item.rstrip('\n')) 161 | 162 | # Prints if matches AMEX 163 | if amex: 164 | i += 1 165 | results.append("\tAMEX:\t\t " + bcolors.FAIL + amex.group().replace(',','').strip() + bcolors.ENDC) 166 | 167 | # Prints if matches VISA 168 | elif visa: 169 | i += 1 170 | results.append("\tVISA:\t\t " + bcolors.FAIL + visa.group().replace(',','').strip() + bcolors.ENDC) 171 | 172 | # Prints if matches Mastercard 173 | elif master: 174 | i += 1 175 | results.append("\tMASTERCARD:\t " + bcolors.FAIL + master.group().replace(',','').strip() + bcolors.ENDC) 176 | 177 | if i > 0: 178 | if a.output: 179 | with open('cardscan.output', "a") as outfile: 180 | outfile.write("File: " + filepath + "\n") 181 | for result in results: 182 | outfile.write(result + "\n") 183 | else: 184 | print ("\nFile: " + filepath) 185 | for result in results: 186 | print result 187 | 188 | except KeyboardInterrupt: 189 | break 190 | except Exception as e: 191 | with open('cardscan4linux-error.log','a') as errlog: 192 | errlog.write("File: " + filepath + "\n" + e + "\n") 193 | sys.exit(bcolors.FAIL + "[*] " + bcolors.ENDC + "Cannot open file '" + filepath + "'.") 194 | except: 195 | sys.exit(bcolors.WARNING + "\r[*] " + bcolors.ENDC + "There are no files that match the search.") 196 | 197 | # Removes the temp file 198 | try: 199 | os.remove("/tmp/cardscan4linux.log") 200 | except OSError: 201 | pass 202 | 203 | total_time = int(timeit.default_timer()) - int(start_time) 204 | # End of file 205 | print (bcolors.OKGREEN + "[*] " + bcolors.ENDC + "Card scanning complete. " + str(file_lines) + " total files were scanned in " + str(total_time) + " seconds.") 206 | if a.output: 207 | print (bcolors.OKGREEN + "[*] " + bcolors.ENDC + "Output saved to " + (os.path.dirname(os.path.realpath(__file__))) + "/cardscan.output.") 208 | --------------------------------------------------------------------------------