├── LICENSE
├── README.md
├── config
├── __init__.py
├── banner.py
├── colors.py
├── config.py
├── footer.py
└── functions.py
├── requirements.txt
└── s3rec0n.py
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Ebryx, LLC
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # S3Rec0n
2 | [](https://github.com/ellerbrock/open-source-badge/)
3 | [](https://www.python.org/downloads/)
4 | [](http://badge.fury.io/gh/boennemann%2Fbadges)
5 | [](https://github.com/ellerbrock/open-source-badge/)
6 |
7 | **A colorful cross-platform python utility to test misconfigurations of buckets both through authenticated and unauthenticated checks!**
8 |
9 |
10 |
11 | ### Requirements
12 |
13 | - Python `(3.7.*)`
14 | - Python `pip3`
15 | - Python module `boto3`
16 | - Python module `botocore`
17 | - Python module `jmespath`
18 | - Python module `pygments`
19 | - Python module `requests`
20 |
21 | ### Install python && modules
22 |
23 | sudo apt install python3 python3-pip python3-venv
24 | mkdir ~/.venvs/S3Rec0n/ && python3 -m venv ~/.venvs/S3Rec0n/ && source ~/.venvs/S3Rec0n/bin/activate
25 | pip install -r requirements.txt
26 |
27 | ### Tested on
28 |
29 | - Pop! OS 18.04
30 | - Kali linux (2019.1)
31 | - Ubuntu 18.04 LTS
32 | - Windows 8/8.1/10
33 | - Subsystem Linux
34 |
35 | ### Download/Clone S3Rec0n
36 |
37 | You can download the latest version of S3Rec0n by cloning the GitHub repository. As a best practice, please use python's virtual environment (venv) while running the script to avoid any modules/packages installation errors.
38 |
39 | git clone https://github.com/Ebryx/S3Rec0n
40 |
41 | ### Usage
42 |
43 | ***Initializing Script***
44 |
45 | python s3rec0n.py
46 |
47 | ***Listing Bucket without S3 API Authorization (anonymously)***
48 |
49 | python s3rec0n.py --unauthorized --list-bucket --bucket=myTestBucket
50 |
51 | ***Listing Bucket with S3 API Authorization (using access keys)***
52 |
53 | python s3rec0n.py --authorized --list-bucket --bucket=myTestBucket
54 |
55 | ***Listing Bucket without specifying any flag both auth/unauth S3 API Call (by default it gets set to unauthorized)***
56 |
57 | python s3rec0n.py --list-bucket --bucket=myTestBucket
58 |
59 | ***Fetching ACL of the Bucket without S3 API Authorization (anonymously)***
60 |
61 | python s3rec0n.py --unauthorized --get-acl --bucket=myTestBucket
62 |
63 | ***Putting/Over-writing the ACL of the Bucket without S3 API Authorization (anonymously)***
64 |
65 | python s3rec0n.py --unauthorized --put-acl --bucket=myTestBucket
66 |
67 | ***Fetching readable objects of the Bucket without S3 API Authorization (anonymously)***
68 |
69 | python s3rec0n.py --unauthorized --readable-objs --bucket=myTestBucket
70 |
71 | ***Trying and uploading a test object on the Bucket without S3 API Authorization (anonymously)***
72 |
73 | python s3rec0n.py --unauthorized --upload-objs --bucket=myTestBucket
74 |
75 | ***Fetching ACLs of all the objects of the Bucket without S3 API Authorization (anonymously)***
76 |
77 | python s3rec0n.py --unauthorized --fetch-obj-acl --bucket=myTestBucket
78 |
79 | ### Description of Checks
80 |
81 | Usage: python s3rec0n.py
82 | Features/Functions:
83 |
84 | 1). Authenticated Checks (through access keys)
85 | 2). Unauthenticated Checks (anonymously)
86 | 3). Buckets Location (AWS Region)
87 | 4). Static Website Hosting Check
88 | 5). Bucket Listing
89 | 6). Fetching ACL (Access Control List) of the Bucket
90 | 7). Over-writing ACL of the bucket (be careful!)
91 | 8). Finding readable objects in the bucket
92 | 9). Uploading test key/object for misconfiguration test
93 | 10). Fetch ACLs of all the Objects
94 |
95 | Example:
96 | python s3rec0n.py
97 |
98 |
99 | ### Some GIFS
100 |
101 | Feel free to make pull requests!
109 | P.S ~ Dont Change The Colors. They're Butiphul like this.
110 | ~ An0n 3xPloiTeR
111 |
112 |
--------------------------------------------------------------------------------
/config/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | ___________________________ __ ________
3 | / _____/\_____ \______ \__ __ _______/ |\_____ \______
4 | \_____ \ _(__ <| | _/ | \/ ___/\ __\_(__ <_ __ \
5 | / \ / \ | \ | /\___ \ | | / \ | \/
6 | /_______ //______ /______ /____//____ > |__|/______ /__|
7 | \/ \/ \/ \/ \/
8 | """
9 |
10 | from config.functions import *
11 | from config.colors import *
12 | from config.config import *
13 | from config.banner import *
14 | from config.footer import *
15 |
--------------------------------------------------------------------------------
/config/banner.py:
--------------------------------------------------------------------------------
1 | import config
2 |
3 | banner = f"""{config.colors.c} ________ _______ _______ ____ ____ ________ ___________ _______ _______
4 | /" )/" __ )| _ "\\ (" _||_ " | /" )(" _ ")/" __ ) /" \\
5 | {config.colors.g}(: \\___/(__/ _) ./(. |_) :)| ( ) : |(: \\___/ )__/ \\\\__/(__/ _) ./|: |
6 | \\___ \\ / // |: \\/ (: | | . ) \\___ \\ \\\\_ / / // |_____/ )
7 | {config.colors.r} __/ \\\\ __ \\_ \\\\ (| _ \\\\ \\\\ \\__/ // __/ \\\\ |. | __ \\_ \\\\ // /
8 | /" \\ :)(: \\__) :\\|: |_) :) /\\\\ __ //\\ /" \\ :) \\: | (: \\__) :\\|: __ \\ {config.colors.w}~> By: {config.colors.g}Syed Umar Arfeen
9 | {config.colors.y}(_______/ \\_______)(_______/ (__________)(_______/ \\__| \\_______)|__| \\___) {config.colors.w}~> Version: {config.colors.g}{config.config.version}
10 | """
11 |
--------------------------------------------------------------------------------
/config/colors.py:
--------------------------------------------------------------------------------
1 | from colorama import init,Fore,Back,Style
2 | import os
3 |
4 | if os.name == "posix":
5 | init(autoreset=True)
6 | # colors foreground text:
7 | fc = "\033[0;96m"
8 | fg = "\033[0;92m"
9 | fw = "\033[0;97m"
10 | fr = "\033[0;91m"
11 | fb = "\033[0;94m"
12 | fy = "\033[0;33m"
13 | fm = "\033[0;35m"
14 |
15 | # colors background text:
16 | bc = "\033[46m"
17 | bg = "\033[42m"
18 | bw = "\033[47m"
19 | br = "\033[41m"
20 | bb = "\033[44m"
21 | by = "\033[43m"
22 | bm = "\033[45m"
23 |
24 | # colors style text:
25 | sd = Style.DIM
26 | sn = Style.NORMAL
27 | sb = Style.BRIGHT
28 |
29 | c = fc
30 | g = fg
31 | w = fw
32 | r = fr
33 | b = fb
34 | y = fy
35 | m = fm
36 |
37 | else:
38 | init(autoreset=True)
39 | # colors foreground text:
40 | fc = Fore.CYAN
41 | fg = Fore.GREEN
42 | fw = Fore.WHITE
43 | fr = Fore.RED
44 | fb = Fore.BLUE
45 | fy = Fore.YELLOW
46 | fm = Fore.MAGENTA
47 |
48 | # colors background text:
49 | bc = Back.CYAN
50 | bg = Back.GREEN
51 | bw = Back.WHITE
52 | br = Back.RED
53 | bb = Back.BLUE
54 | by = Fore.YELLOW
55 | bm = Fore.MAGENTA
56 |
57 | # colors style text:
58 | sd = Style.DIM
59 | sn = Style.NORMAL
60 | sb = Style.BRIGHT
61 |
62 | c = fc + sb
63 | g = fg + sb
64 | w = fw + sb
65 | r = fr + sb
66 | b = fb + sb
67 | y = fy + sb
68 | m = fm + sb
69 |
--------------------------------------------------------------------------------
/config/config.py:
--------------------------------------------------------------------------------
1 | from config import *
2 | from sys import argv
3 |
4 | """
5 | ------------------------------------------------------------------
6 | Argparse Variables
7 | ------------------------------------------------------------------
8 | """
9 |
10 | version = "1.0"
11 |
12 | _usage = f"\r{w}[{c}#{w}] Usage: python {g}{argv[0]}{w} --bucket myTestBucket --all"
13 | _version = "{}[{}~{}] {}Version: {}{}".format(w,c,w,w,g,version)
14 |
--------------------------------------------------------------------------------
/config/footer.py:
--------------------------------------------------------------------------------
1 | from config import *
2 |
3 | footer = """
4 | {white}[{green}${white}] {cyan}Thanks For Using {white}:D
5 | \t{blue}~ {red}An0n 3xPloiTeR {white}:)""".format(
6 | white = w,
7 | green = g,
8 | cyan = c,
9 | blue = b,
10 | red = r
11 | )
12 |
--------------------------------------------------------------------------------
/config/functions.py:
--------------------------------------------------------------------------------
1 | from config.colors import *
2 |
3 | def bucketExists(bucketName):
4 | bucket = f"http://{bucketName}.s3.amazonaws.com"
5 | request = requests.get(bucket)
6 |
7 | if request.status_code == 200:
8 | return(True) # Bucket Exists and is:listable
9 |
10 | elif request.status_code == 403:
11 | return(True) # Bucket Exists and Access's Denied.
12 |
13 | else:
14 | return(False) # Get it ;)
15 |
16 | def deleteFile(bucketName, fileName):
17 | """
18 | if `file` exists: then delete
19 | """
20 | path = os.getcwd()
21 | reports_dir = os.path.join(path, 'reports')
22 | bucket_pth = os.path.join(reports_dir, bucketName)
23 | file = os.path.join(bucket_pth, fileName)
24 |
25 | if os.path.isfile(file):
26 | os.remove(file)
27 |
28 | def write(var, color, data):
29 | if var == None:
30 | print(color + str(data))
31 | elif var != None:
32 | print(w + "[" + g + var + w + "] " + color + str(data))
33 |
34 | def _heading(heading, c, var):
35 | name = var
36 | # name = u'\u2500'
37 | space = " " * 7
38 | var = str(space + heading + " ..." + space)
39 | length = len(var) + 1; print() # \n
40 | print("{white}" + name * length + name).format(white=w)
41 | print("{color}" + var).format(color=c)
42 | print("{white}" + name * length + name).format(white=w); print() # \n
43 |
44 | def report(bucketName, fileName, stdout, opt="w"):
45 | """
46 | Useful in dumping output of stdout into /reports/bucketName/specified-file.ext
47 | Will be integrated soon!
48 | """
49 | path = os.getcwd()
50 | reports_dir = os.path.join(path, 'reports')
51 | bucket_pth = os.path.join(reports_dir, bucketName)
52 | file = os.path.join(bucket_pth, fileName)
53 |
54 | if not(os.path.isdir(reports_dir)):
55 | os.mkdir(reports_dir)
56 | if not(os.path.isdir(bucket_pth)):
57 | os.mkdir(bucket_pth)
58 |
59 | elif os.path.isdir(reports_dir):
60 | if not(os.path.isdir(bucket_pth)):
61 | os.mkdir(bucket_pth)
62 |
63 | with open(file, opt) as f:
64 | f.write(stdout)
65 |
66 | def heading(heading, bucket, color, afterWebHead):
67 | space = " " * 15
68 | var = str(space + heading + " '" + bucket + "'" + str(afterWebHead) + " ..." + space)
69 | length = len(var) + 1
70 |
71 | print()
72 | print(w + "-" * length + "-")
73 | print(color + var)
74 | print(w + "-" * length + "-")
75 | print()
76 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | boto3
2 | botocore
3 | colorama
4 | jmespath
5 | Pygments
6 | requests
7 |
--------------------------------------------------------------------------------
/s3rec0n.py:
--------------------------------------------------------------------------------
1 | from jmespath import search as queryJson
2 | from re import findall as parseRegex
3 | from pygments import highlight, lexers, formatters
4 | from requests import get
5 | from json import loads, dumps
6 | from time import sleep
7 | from io import StringIO
8 | from config import *
9 | from config.config import _usage
10 | import botocore
11 | import boto3
12 | import argparse
13 |
14 | class S3:
15 | def __init__(self, bucket):
16 | self.bucket = bucket
17 |
18 | def parseJson(self, query, jsonObj):
19 | return(queryJson(query, jsonObj))
20 |
21 | def prettifyJSON(self, jsonObject):
22 | return(highlight(jsonObject, lexers.JsonLexer(), formatters.TerminalFormatter()))
23 |
24 | def authorizedClientCall(self):
25 | """
26 | Makes Authorized Client call to S3 (picks up access keys from ~/.aws/)
27 | """
28 | client = boto3.client("s3")
29 | return(client)
30 |
31 | def unAuthorizedClientCall(self):
32 | """
33 | Makes Unauthorized Client call to S3 (i.e. without access keys)
34 | """
35 | client = boto3.client("s3", config=botocore.config.Config(signature_version=botocore.UNSIGNED))
36 | return(client)
37 |
38 | def getBucketLocation(self, bucketName):
39 | """
40 | Returns bucket['LocationConstraint']
41 | Doesn't work with unsigned requests
42 | So, had to come up with a solution of mine B)
43 | """
44 | _bucket = f"http://{bucketName}.s3.eu-west-1.amazonaws.com"
45 | request = get(_bucket)
46 | sourceCode = request.content.decode('UTF-8')
47 | regex = r'\