├── .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 | 
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 |
--------------------------------------------------------------------------------