├── README.TXT ├── README.md ├── docs ├── AddingNewIssuesAndSecurityChecks.md ├── BuildingTheExecutable.md ├── MissingSecurityPatchesVsPublicExploits.md └── QuickStartUsage.md ├── windows-privesc-check2.exe ├── windows_privesc_check.py ├── windows_privesc_check.spec ├── wpc ├── __init__.py ├── ace.py ├── acelist.py ├── audit │ ├── __init__.py │ ├── audit.py │ ├── auditbase.py │ ├── dump.py │ └── dumptab.py ├── cache.py ├── conf.py ├── drive.py ├── drives.py ├── exploit.py ├── file.py ├── files.py ├── group.py ├── groups.py ├── mspatchdb.py ├── ntobj.py ├── parseOptions.py ├── patchdata.py ├── principal.py ├── process.py ├── processes.py ├── regkey.py ├── report │ ├── __init__.py │ ├── appendices.py │ ├── appendix.py │ ├── fileAcl.py │ ├── issue.py │ ├── issueAcl.py │ ├── issues.py │ └── report.py ├── scheduledtask.py ├── scheduledtasks.py ├── sd.py ├── service.py ├── services.py ├── share.py ├── shares.py ├── softwarepackage.py ├── softwarepackages.py ├── thread.py ├── token.py ├── user.py ├── users.py └── utils.py └── xsl ├── html.xsl └── text.xsl /README.TXT: -------------------------------------------------------------------------------- 1 | [ What is wpc-2.0? ] 2 | 3 | The development branch for the next generation of windows-privesc-check. 4 | 5 | [ Should I use it? ] 6 | 7 | You can try it, but trunk version has more features and HTML reports. 8 | 9 | The new version has a nicer code base, but very few features at present and text-only reports. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Windows-privesc-check is standalone executable that runs on Windows systems. It tries to find misconfigurations that could allow local unprivileged users to escalate privileges to other users or to access local apps (e.g. databases). 2 | 3 | It is written in python and converted to an executable using pyinstaller so it can be easily uploaded and run (as opposed to unzipping python + other dependencies). It can run either as a normal user or as Administrator (obviously it does a better job when running as Administrator because it can read more files). 4 | 5 | The latest version of the code is in the master branch. 6 | 7 | # Use Cases 8 | 9 | Below is a high level description of common use cases. See also the [Quick Start & Usage](docs/QuickStartUsage.md) page. 10 | 11 | ## Find Privesc Vectors (as Administrator) 12 | 13 | When run with admin rights, windows-privesc-check has full read access to all [secureable objects](http://msdn.microsoft.com/en-us/library/aa379557%28VS.85%29.aspx). This allows it to perform audits for escalation vectors such as: 14 | * Reconfiguring Windows Services 15 | * Replacing Service executables if they have weak file permissions 16 | * Replacing poorly protected .exe or .dll files in %ProgramFiles% 17 | * Tojaning the %PATH% 18 | * Maliciously modifying the registry (e.g. RunOnce) 19 | * Modifying programs on FAT file systems 20 | * Tampering with running processes 21 | 22 | A great many of the privielges escalation vectors checked are simply checks for weak security descriptors on [Windows securable objects](http://msdn.microsoft.com/en-us/library/aa379557%28VS.85%29.aspx). 23 | 24 | A report is generated in HTML, TXT and XML format. 25 | 26 | ## Find Privesc Vectors (as a Low-Privileged User) 27 | 28 | An important design goal is that windows-privesc-check can perform as many checks as possible (above) without admin rights. This will make the tool useful to pentesters as well as auditors. 29 | 30 | Clearly, low-privileged users are unable to see certain parts of the registry and file system. The tool is therefore inherently less able to identify security weaknesses when run as a low-privileged user. 31 | 32 | As above, a report is generated in HTML, TXT and XML format. 33 | 34 | ## Dump Raw Auditing Data 35 | 36 | Windows-privesc-check can simply dump raw data that it would normally use to identify security weaknesses. This data can then analysed some other way - or simply stored as a snapshot of system security at the time of the audit. 37 | 38 | Both human-readable (text) and machine readable (tab delimited) formats are supported. 39 | 40 | Examples of data users are able to dump: 41 | * Detailed Share Information about local or remote systems. Includes DACL (Share permissions). 42 | * Information about users, groups, memeberships and the Windows Privileges (e.g. SeBackupPrivilege). See http://msdn.microsoft.com/en-us/library/bb530716%28v=VS.85%29.aspx. 43 | 44 | ## Provide Information To Help Compromise A Remote System 45 | 46 | Given low-privileged credentials (or perhaps using anonymous access), windows-privesc-check should provide basic information which might help the user compromise the remote system. This might include: 47 | * Details of poorly configure shares 48 | * A list of admin-equivalent users 49 | * Information about its domain membership and the trusts configured for that domain 50 | 51 | # Disclaimer 52 | 53 | Run this tool against your own systems at your own risk. 54 | 55 | Run this tool against someone else's system only with their informed consent and with the appropriate legal permissions. 56 | 57 | This tool has been known to cause high CPU load and high disk I/O. 58 | 59 | ## Intended Use 60 | 61 | This tool is intended to be run by security auditors and penetration testers against systems they have been engaged to assess, and also by system administrators who want to check for "obvious" misconfigurations. It can even be run as a Scheduled Task so you can check regularly for misconfigurations that might be introduced. 62 | 63 | Ensure that you have the appropriate legal permission before running it someone else's system. 64 | 65 | ## Licence 66 | 67 | This tool may be used for legal purposes only. Users take full responsibility for any actions performed using this tool. The author accepts no liability for damage caused by this tool. If you do not accept these condition then you are prohibited from using this tool. 68 | 69 | In all other respects the GPL version 2 applies: 70 | 71 | This program is free software; you can redistribute it and/or modify 72 | it under the terms of the GNU General Public License version 2 as 73 | published by the Free Software Foundation. 74 | 75 | This program is distributed in the hope that it will be useful, 76 | but WITHOUT ANY WARRANTY; without even the implied warranty of 77 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 78 | GNU General Public License for more details. 79 | 80 | You should have received a copy of the GNU General Public License along 81 | with this program; if not, write to the Free Software Foundation, Inc., 82 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 83 | -------------------------------------------------------------------------------- /docs/AddingNewIssuesAndSecurityChecks.md: -------------------------------------------------------------------------------- 1 | # How to add new issues to windows-privesc-check # 2 | 3 | There are essentially 3 things you need to do to add a new issue: 4 | 1. Write the code that checks for the presence of the issue (obviously) 5 | 1. Write the text of the issue 6 | 1. (perhaps also) write some code to include supporting data in your issue text 7 | 8 | ## 1. Writing the security check ## 9 | 10 | You should add your check to windows-privesc-check.py. Here's an example check: 11 | 12 | ``` 13 | # Check that the binary name is properly quoted 14 | if str(s.get_exe_path_clean()).find(" ") > 0: # clean path contains a space 15 | if str(s.get_exe_path()).find(str('"' + s.get_exe_path_clean()) + '"') < 0: # TODO need regexp. Could get false positive from this. 16 | report.get_by_id("WPC051").add_supporting_data('service_info', [s]) 17 | ``` 18 | 19 | The important thing to note is eventually you'll end up calling "report.get\_by\_id("WPCNNN").add\_supporting\_data('type of supporting data', [datastructure, datastructure, ...] 20 | 21 | The issue reference "WPCNNN" is described below, followed by how the supporting data works. 22 | 23 | ## 2. Writing the issue ## 24 | 25 | Open up wpc/conf.py and locate the (long) dictionary "issue\_template". You'll see issue text for WPC001, WPC002, ... Add your issue to the end of it. Copy and paste an earlier one as a template. The only part likely to cause confusion is the "supporting\_data" bit which is explained below. 26 | 27 | ## 3. Adding supporting data ## 28 | 29 | In wpc/conf.py you'll have something that looks like this: 30 | ``` 31 | 'supporting_data': { 32 | 'service_info': { 33 | 'section': "description", 34 | 'preamble': "The following services have insecurely quoted paths:", 35 | }, 36 | } 37 | ``` 38 | 39 | It describes how to add supporting data to an issue. 40 | * section: States whether the supporting data appears under the "description" or the "recommendation". You usally want "description". 41 | * preamble: Is some static text that goes before your supporting data. 42 | * service\_info: this is the type of supporting data. It's just a string you choose. If another issue already reports supporting data in the format you want, use it. If not you'll have to define your own. The string you use must match the type of supporting data saved when you called "add\_supporting\_data" from windows-privesc-check.py: 43 | ``` 44 | report.get_by_id("WPC051").add_supporting_data('service_info', [s]) 45 | ``` 46 | 47 | Any data structure you need to generate your supporting data must be passed as the second argument to "add\_supporting\_data". 48 | 49 | So how does wpc know how to display the supporting data? Open up wpc/report/issue.py and locate the "render\_supporting\_data" method. It contains an "if" statement for every possible type of supporting data - "service\_info" is an example of a type of supporting data. It is called to convert the data structures you passed to "add\_supporting\_data" into text for the report. 50 | 51 | You should check the supporting data type you want to use is in issue.py. Add a new "if" clause for it if not. 52 | -------------------------------------------------------------------------------- /docs/BuildingTheExecutable.md: -------------------------------------------------------------------------------- 1 | The source code for windows-privesc-check is Python. This page describes how to create a Windows executable (.exe file) using pyinstaller. 2 | 3 | The process below was tested on Windows XP. 4 | 5 | # Install Dependencies # 6 | 7 | In order to run windows-privesc-check.exe, you don't need any dependencies installed. However, to build the .exe yourself, you'll need to following: 8 | * python: http://www.python.org/download/releases/2.7.1/ 9 | * pyinstaller: http://www.pyinstaller.org/changeset/1352/tags/1.5-rc2?old_path=%2F&format=zip 10 | * pywin32: http://sourceforge.net/projects/pywin32/files/pywin32/Build%20214/ 11 | * lxml: http://lxml.de/index.html 12 | 13 | Other versions will work too - these are just an example of known working versions. 14 | 15 | # Build Executable # 16 | 17 | * Unzip pyinstaller to c:\pyinstaller 18 | * cd c:\pyinstaller 19 | * python Configure.py 20 | * python Makespec.py --onefile c:\somepath\windows-privesc-check.py 21 | * python Build.py windows-privesc-check\windows-privesc-check.spec 22 | 23 | This should create the following .exe for you: 24 | windows-privesc-check\dist\window-privesc-check.exe 25 | 26 | # Useful Links # 27 | 28 | These resources are incredibly useful: 29 | * pywin32 online docs: http://timgolden.me.uk/pywin32-docs/contents.html 30 | * pyinstaller docs: http://www.pyinstaller.org/export/latest/tags/1.4/doc/Manual.html?format=raw 31 | -------------------------------------------------------------------------------- /docs/MissingSecurityPatchesVsPublicExploits.md: -------------------------------------------------------------------------------- 1 | # The -T option checks for missing patches and advises about public exploits 2 | 3 | # Using -T Option To Suggest Public Exploits # 4 | 5 | [Windows-privesc-check](http://code.google.com/p/windows-privesc-check/wiki/DesignGoals) can use the [Security Bulletin](http://technet.microsoft.com/en-us/security/gg447546) information from the [Microsoft spreadsheet](http://go.microsoft.com/fwlink/?LinkId=245778) to determine which patches are missing. 6 | 7 | A list of [Metasploit](http://www.metasploit.com/) exploits is currently hardcoded. 8 | 9 | Windows-privesc-check correlates the two and adds an issue to the report if public exploits are available for a system. 10 | 11 | This all works as low-privileged user - you don't have to be in the local administrators group. This can be helpful during pentests: [MBSA](http://technet.microsoft.com/en-us/security/cc184924) and other patch checking tools require administrative rights. 12 | 13 | # Example 1: Download patch data from Internet # 14 | 15 | Use "-T auto" to have windows-privesc-check connect to the MS site over the Internet and download the patch spreadsheet. This is the simplest way to use the feature if you have Internet connectivity: 16 | 17 | ``` 18 | C:\>windows-privesc-check2.exe --audit -T auto -o report 19 | windows-privesc-check v2.0svn92 (http://pentestmonkey.net/windows-privesc-check) 20 | ... 21 | ============ Starting Audit ============ 22 | 23 | [+] Running: audit_patches 24 | [-] Attempting to download patch info from Microsoft... 25 | [-] Download complete 26 | [-] Gathering installed patches 27 | [-] 1 patches are installed 28 | [-] OS string for Microsoft spreadsheet is: Microsoft Windows Server 2003 29 | [-] Found 21 exploits potentially affecting this system 30 | 31 | ============ Audit Complete ============ 32 | 33 | [+] Saving report file report.txt 34 | [+] Saving report file report.html 35 | ``` 36 | 37 | # Example 2: Use a local copy of the patch spreadsheet # 38 | 39 | Assuming you've downloaded the [spreadsheet](http://go.microsoft.com/fwlink/?LinkId=245778) and saved it as ms-patch-info.xlsx on the system you're auditing (in the same directory as windows-privesc-check is run from) you can use the -T option without an Internet connection: 40 | 41 | ``` 42 | C:\>windows-privesc-check2.exe --audit -T ms-patch-info.xlsx -o report 43 | windows-privesc-check v2.0svn92 (http://pentestmonkey.net/windows-privesc-check) 44 | ... 45 | ============ Starting Audit ============ 46 | 47 | [+] Running: audit_patches 48 | [-] Gathering installed patches 49 | [-] 66 patches are installed 50 | [-] OS string for Microsoft spreadsheet is: Windows XP Service Pack 3 51 | [-] Found 0 exploits potentially affecting this system 52 | 53 | ============ Audit Complete ============ 54 | 55 | [+] Saving report file report.txt 56 | [+] Saving report file report.html 57 | ``` 58 | 59 | # Example issue from report # 60 | ``` 61 | C:\>start report.html 62 | ``` 63 | http://windows-privesc-check.googlecode.com/svn/wiki/wpc2-report-patches.PNG 64 | 65 | # Limitations # 66 | 67 | The -T option should be useful for pentests. However, a few false positives have been spotted during testing, so it's not so great for audits where you want to provide a definitive list of patches/exploits to a client. False positives mainly relate to exploits for components that aren't installed - windows-privesc-check doesn't currently check what software is installed, it just uses the OS string to get the relevant patches from the spreadsheet. 68 | 69 | # Download Source / Exe # 70 | 71 | Download from [here](http://code.google.com/p/windows-privesc-check/source/browse/#svn%2Fbranches%2Fwpc-2.0). 72 | -------------------------------------------------------------------------------- /docs/QuickStartUsage.md: -------------------------------------------------------------------------------- 1 | # Quick Start & Usage 2 | 3 | This page describes basic usage of windows-privesc-check along with a few examples for common use cases. 4 | 5 | Also the [main page](../README.md) for a high-level description of features. 6 | 7 | ## Quick Start 8 | 9 | ### Auditing the Local System 10 | 11 | windows-privesc-check is best run on the system you want to audit. There are a few less common use-cases where windows-privesc-check might be run over the network (see below). 12 | 13 | Upload windows-privesc-check2.exe to the system you want to audit. 14 | 15 | #### Run a Security Audit as Administrator 16 | 17 | To most reliably find the highest number of security issues, run a security audit from an elevated command prompt: 18 | ``` 19 | C:>windows-privesc-check2.exe --audit -a -o wpc-report 20 | ``` 21 | 22 | The options means: 23 | * `--audit`: run in audit mode. Relatively little output. Creates a report file (see below). Identifies security issues. 24 | * `-a`: Run all simple checks 25 | * `-o`: File name stem for the reports. 26 | 27 | The output report is saved in the current directory in the following formats: 28 | * wpc-report.html - a list of security issues and appendices of relevant information in HTML format. This report type is recommended for most use cases. 29 | * wpc-report.txt - a list of security issues in text format. The type of report is not as well supported or as useful as the HTML format. 30 | * wpc-report.xml - everything from the HTML report, but in XML format. This can be used to import results into your own tools and scripts. 31 | 32 | #### Check for Privilege Escalation Vectors as a Non-administrative User 33 | 34 | Use the same options as you would went running as an administrator: 35 | ``` 36 | C:\>windows-privesc-check2.exe --audit -a -o wpc-report 37 | ``` 38 | 39 | Some checks will fail as you lack permissions to do some of the checks. Read wpc-report.html for a list of potential escalation vectors. 40 | 41 | #### Dump Data about Securable Objects and other Security Settings 42 | 43 | ``` 44 | C:\>windows-privesc-check2.exe --dump -a > dump.txt 45 | ``` 46 | 47 | This will dump extremely verbose, human-readable data about the target system. Make sure you redirect the output to a file. 48 | 49 | ``` 50 | C:\>windows-privesc-check2.exe --dumptab -a > dump.txt 51 | ``` 52 | 53 | This will dump extremely verbose, machine-readable data about the target system. Tab-delimited format is used. The meaning of each tab-delimited field is currently undocumented, but in many cases is obvious. 54 | 55 | ### Running Over the Network 56 | 57 | windows-privesc-check cannot run any security checks (`--audit`) over the network. However, it can dump a small amount of information that might be useful to penetration testers. 58 | 59 | #### List Logged in Users 60 | 61 | Listing logged on users required administrative privileges to the remote system. The `-L` option lists logged in users. It can be used either with current credentials or with new credentials: 62 | ``` 63 | C:\>windows-privesc-check2.exe --dump -L -s 10.0.0.1 64 | C:\>windows-privesc-check2.exe --dump -L -s 10.0.0.1 -u administrator -p mypass -d mydomain 65 | ``` 66 | 67 | Output can be in human-readable (`--dump`) or machine-readable (`--dumptab`) format: 68 | ``` 69 | C:\>windows-privesc-check2.exe --dump -L -s 10.0.0.1 70 | C:\>windows-privesc-check2.exe --dumptab -L -s 10.0.0.1 71 | ``` 72 | 73 | #### List Shares 74 | 75 | Listing shares normally requires a valid account on the remote systems, but does not need administrative access. 76 | 77 | ``` 78 | C:\>windows-privesc-check2.exe --dump -H -s 10.0.0.1 79 | C:\>windows-privesc-check2.exe --dump -H -s 10.0.0.1 -u administrator -p mypass -d mydomain 80 | C:\>windows-privesc-check2.exe --dumptab -H -s 10.0.0.1 -u administrator -p mypass -d mydomain 81 | ``` 82 | 83 | #### List Users 84 | 85 | Listing users normally requires a valid account on the remote systems, but does not need administrative access. 86 | ``` 87 | C:\>windows-privesc-check2.exe --dump -U -s 10.0.0.1 88 | C:\>windows-privesc-check2.exe --dump -U -s 10.0.0.1 -u administrator -p mypass -d mydomain 89 | C:\>windows-privesc-check2.exe --dumptab -U -s 10.0.0.1 -u administrator -p mypass -d mydomain 90 | ``` 91 | 92 | #### List Group Memberships 93 | 94 | Listing groups members normally requires a valid account on the remote systems, but does not need administrative access. 95 | ``` 96 | C:\>windows-privesc-check2.exe --dump -G -s 10.0.0.1 97 | C:\>windows-privesc-check2.exe --dump -G -s 10.0.0.1 -u administrator -p mypass -d mydomain 98 | C:\>windows-privesc-check2.exe --dumptab -G -s 10.0.0.1 -u administrator -p mypass -d mydomain 99 | ``` 100 | 101 | ## Usage 102 | 103 | The quick-start guide above shows only a few of the main features. A full list of options is shown below: 104 | ``` 105 | windows-privesc-check v2.0svn198 (http://pentestmonkey.net/windows-privesc-check) 106 | 107 | Usage: C:\share\wpc2.exe (--dump [ dump opts] | --dumptab | --audit) [examine opts] [host opts] -o report-file-stem 108 | 109 | Options: 110 | --version show program's version number and exit 111 | -h, --help show this help message and exit 112 | --dump Dumps info for you to analyse manually 113 | --dumptab Dumps info in tab-delimited format 114 | --audit Identify and report security weaknesses 115 | --pyshell Start interactive python shell 116 | 117 | examine opts: 118 | At least one of these to indicate what to examine (*=not implemented) 119 | 120 | -a, --all All Simple Checks (non-slow) 121 | -A, --allfiles All Files and Directories (slow) 122 | -D, --drives Drives 123 | -e, --reg_keys Misc security-related reg keys 124 | -E, --eventlogs Event Log* 125 | -f INTERESTING_FILE_LIST, --interestingfiledir=INTERESTING_FILE_LIST 126 | Changes -A behaviour. Look here INSTEAD 127 | -F INTERESTING_FILE_FILE, --interestingfilefile=INTERESTING_FILE_FILE 128 | Changes -A behaviour. Look here INSTEAD. On dir per 129 | line 130 | -G, --groups Groups 131 | -H, --shares Shares 132 | -I, --installed_software 133 | Installed Software 134 | -j, --tasks Scheduled Tasks 135 | -k, --drivers Kernel Drivers 136 | -L, --loggedin Logged In 137 | -O, --ntobjects NT Objects 138 | -n, --nointerestingfiles 139 | Changes -A/-f/-F behaviour. Don't report interesting 140 | files 141 | -N, --nounreadableif 142 | Changes -A/-f/-F behaviour. Report only interesting 143 | files readable by untrsuted users (see -x, -X, -b, -B) 144 | -P, --progfiles Program Files Directory Tree 145 | -r, --registry Registry Settings + Permissions 146 | -R, --processes Processes 147 | -S, --services Windows Services 148 | -t, --paths PATH 149 | -T PATCHFILE, --patches=PATCHFILE 150 | Patches. Arg is filename of xlsx patch info. 151 | Download from 152 | http://go.microsoft.com/fwlink/?LinkID=245778 or pass 153 | 'auto' to fetch automatically 154 | -U, --users Users 155 | -v, --verbose More verbose output on console 156 | -W, --errors Die on errors instead of continuing (for debugging) 157 | -z, --noappendices No report appendices in --audit mode 158 | 159 | host opts: 160 | Optional details about a remote host (experimental). Default is 161 | current host. 162 | 163 | -s REMOTE_HOST, --server=REMOTE_HOST 164 | Remote host or IP 165 | -u REMOTE_USER, --user=REMOTE_USER 166 | Remote username 167 | -p REMOTE_PASS, --pass=REMOTE_PASS 168 | Remote password 169 | -d REMOTE_DOMAIN, --domain=REMOTE_DOMAIN 170 | Remote domain 171 | 172 | dump opts: 173 | Options to modify the behaviour of dump/dumptab mode 174 | 175 | -M, --get_modals Dump password policy, etc. 176 | -V, --get_privs Dump privileges for users/groups 177 | 178 | report opts: 179 | Reporting options 180 | 181 | -o REPORT_FILE_STEM, --report_file_stem=REPORT_FILE_STEM 182 | Filename stem for txt, html report files 183 | -x IGNORE_PRINCIPAL_LIST, --ignoreprincipal=IGNORE_PRINCIPAL_LIST 184 | Don't report privesc issues for these users/groups 185 | -X IGNORE_PRINCIPAL_FILE, --ignoreprincipalfile=IGNORE_PRINCIPAL_FILE 186 | Don't report privesc issues for these users/groups 187 | -0, --ignorenoone No one is trusted (even Admin, SYSTEM). hyphen zero 188 | -c, --exploitablebycurrentuser 189 | Report only privesc issues relating to current user 190 | -b EXPLOITABLE_BY_LIST, --exploitableby=EXPLOITABLE_BY_LIST 191 | Report privesc issues only for these users/groups 192 | -B EXPLOITABLE_BY_FILE, --exploitablebyfile=EXPLOITABLE_BY_FILE 193 | Report privesc issues only for these user/groupss 194 | ``` 195 | -------------------------------------------------------------------------------- /windows-privesc-check2.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pentestmonkey/windows-privesc-check/9f304fdf317536c41120f305f62a37bdeefddfdc/windows-privesc-check2.exe -------------------------------------------------------------------------------- /windows_privesc_check.py: -------------------------------------------------------------------------------- 1 | from wpc.parseOptions import parseOptions 2 | from wpc.report.report import report 3 | from wpc.audit.dump import dump 4 | from wpc.audit.dumptab import dumptab 5 | from wpc.audit.audit import audit 6 | import datetime 7 | import time 8 | import wpc.utils 9 | import sys 10 | 11 | # ------------------------ Main Code Starts Here --------------------- 12 | 13 | # Parse command line arguments 14 | options = parseOptions() 15 | 16 | # Initialise WPC 17 | # TODO be able to enable/disable caching 18 | wpc.utils.init(options) 19 | 20 | # Object to hold all the issues we find 21 | report = report() 22 | wpc.utils.populate_scaninfo(report) 23 | issues = report.get_issues() 24 | 25 | if options.pyshell_mode: 26 | wpc.utils.printline("Python Shell - to exit do CTRL-z or type exit()") 27 | print 28 | import code 29 | code.interact(local=dict(globals(), **locals())) 30 | sys.exit() 31 | 32 | wpc.utils.dump_options(options) 33 | 34 | wpc.utils.printline("Starting Audit at %s" % datetime.datetime.strftime(datetime.datetime.now(), '%Y-%m-%d %H:%M:%S')) 35 | start_time = time.time() 36 | 37 | # Dump raw data if required 38 | if options.dump_mode: 39 | d = dump(options) 40 | d.run() 41 | 42 | # Dump raw data if required 43 | if options.dumptab_mode: 44 | d = dumptab(options, report) 45 | d.run() 46 | 47 | # Identify security issues 48 | if options.audit_mode: 49 | a = audit(options, report) 50 | a.run() 51 | 52 | if options.report_file_stem: 53 | wpc.utils.printline("Audit Complete at %s" % datetime.datetime.strftime(datetime.datetime.now(), '%Y-%m-%d %H:%M:%S')) 54 | print 55 | print "[+] Runtime: %.1f seconds" % int(time.time() - start_time) 56 | print 57 | 58 | filename = "%s.xml" % options.report_file_stem 59 | print "[+] Saving report file %s" % filename 60 | f = open(filename, 'w') 61 | f.write(report.as_xml_string()) 62 | f.close() 63 | 64 | filename = "%s.txt" % options.report_file_stem 65 | print "[+] Saving report file %s" % filename 66 | f = open(filename, 'w') 67 | f.write(report.as_text()) 68 | f.close() 69 | 70 | filename = "%s.html" % options.report_file_stem 71 | print "[+] Saving report file %s" % filename 72 | f = open(filename, 'w') 73 | f.write(report.as_html()) 74 | f.close() 75 | 76 | #wpc.conf.cache.print_stats() 77 | -------------------------------------------------------------------------------- /windows_privesc_check.spec: -------------------------------------------------------------------------------- 1 | # -*- mode: python -*- 2 | def Datafiles(*filenames, **kw): 3 | import os 4 | 5 | def datafile(path, strip_path=True): 6 | parts = path.split('/') 7 | path = name = os.path.join(*parts) 8 | if strip_path: 9 | name = os.path.basename(path) 10 | return name, path, 'DATA' 11 | 12 | strip_path = kw.get('strip_path', True) 13 | return TOC( 14 | datafile(filename, strip_path=strip_path) 15 | for filename in filenames 16 | if os.path.isfile(filename)) 17 | 18 | a = Analysis([os.path.join(HOMEPATH,'support\\_mountzlib.py'), os.path.join(HOMEPATH,'support\\useUnicode.py'), 'C:\\wpc2\\windows-privesc-check\\windows_privesc_check.py'], 19 | pathex=['C:\\pyinstaller-1.5-rc2']) 20 | a.datas = Tree('c:\\wpc2\\windows-privesc-check\\xsl') 21 | pyz = PYZ(a.pure) 22 | exe = EXE( pyz, 23 | a.scripts, 24 | a.binaries, 25 | a.zipfiles, 26 | a.datas, 27 | Datafiles('/wpc2/windows-privesc-check/xsl/html.xsl', '/wpc2/windows-privesc-check/xsl/text.xsl'), 28 | name=os.path.join('dist', 'windows_privesc_check.exe'), 29 | debug=False, 30 | strip=False, 31 | upx=True, 32 | console=True , resources=[], 33 | ) 34 | 35 | 36 | -------------------------------------------------------------------------------- /wpc/__init__.py: -------------------------------------------------------------------------------- 1 | pass 2 | -------------------------------------------------------------------------------- /wpc/ace.py: -------------------------------------------------------------------------------- 1 | from wpc.principal import principal 2 | import ntsecuritycon 3 | import wpc.conf 4 | 5 | 6 | class ace: 7 | def __init__(self, otype, ace): 8 | self.set_ace(ace) 9 | self.type = None 10 | self.resolved_perms = [] 11 | self.set_otype(otype) 12 | self.set_type_i(ace[0][0]) 13 | self.set_flags(ace[0][1]) 14 | self.set_sid(ace[2]) 15 | self.set_dperms([]) 16 | self.set_dpermsread([]) 17 | self.set_principal(principal(ace[2])) 18 | self.set_perms(self.resolve_perms()) 19 | 20 | def get_type(self): 21 | if not self.type: 22 | for i in ("ACCESS_ALLOWED_ACE_TYPE", "ACCESS_DENIED_ACE_TYPE", "SYSTEM_AUDIT_ACE_TYPE", "SYSTEM_ALARM_ACE_TYPE"): 23 | if getattr(ntsecuritycon, i) == self.type_i: 24 | # Abbreviate 25 | if i == "ACCESS_ALLOWED_ACE_TYPE": 26 | self.type = "ALLOW" 27 | break 28 | if i == "ACCESS_DENIED_ACE_TYPE": 29 | self.type = "DENY" 30 | break 31 | if not self.type: 32 | self.type = "UNKNOWN_ACE_TYPE_" + self.type_i 33 | return self.type 34 | 35 | def get_sid(self): 36 | return self.sid 37 | 38 | def get_flags(self): 39 | return self.flags 40 | 41 | def set_principal(self, principal): 42 | self.principal = principal 43 | 44 | def set_dperms(self, dperms): 45 | self.dperms = dperms 46 | 47 | def set_dpermsread(self, dpermsread): 48 | self.dpermsread = dpermsread 49 | 50 | def set_sid(self, sid): 51 | self.sid = sid 52 | 53 | def set_flags(self, flags): 54 | self.flags = flags 55 | 56 | def set_ace(self, ace): 57 | self.ace = ace 58 | 59 | def set_type_i(self, type_i): 60 | self.type_i = type_i 61 | 62 | def set_otype(self, otype): 63 | self.otype = otype 64 | 65 | def set_type(self, principal_type): 66 | self.type = principal_type 67 | 68 | def get_principal(self): 69 | return self.principal 70 | 71 | def get_otype(self): 72 | return self.otype 73 | 74 | def resolve_perms(self): 75 | if self.resolved_perms == []: 76 | for mod, perms_tuple in wpc.conf.all_perms[self.get_otype()].iteritems(): 77 | for perm in perms_tuple: 78 | g = getattr(mod, perm) # save a getattr call 79 | if g & self.ace[1] == g: 80 | self.resolved_perms.append(perm) 81 | return self.resolved_perms 82 | 83 | def get_perms(self): 84 | return self.perms 85 | 86 | def get_ace(self): 87 | return self.ace 88 | 89 | def copy(self): 90 | new = ace(self.get_otype(), self.get_ace()) 91 | return new 92 | 93 | def set_perms(self, perms): 94 | self.perms = perms 95 | 96 | def has_perm(self, perm): 97 | if self.get_type() == "ALLOW": # we ignore DENY aces - mostly correct TODO they're actually checked before ALLOWs. False negatives if user is blocked by DENY 98 | for p in self.get_perms(): 99 | if p == perm: 100 | return 1 101 | return 0 102 | 103 | def get_perms_dangerous(self): 104 | if self.dperms == []: 105 | if self.get_type() == "ALLOW": # we ignore DENY aces - mostly correct TODO they're actually checked before ALLOWs. False negatives if user is blocked by DENY 106 | for p in self.get_perms(): 107 | for k in wpc.conf.dangerous_perms_write[self.get_otype()]: 108 | if p in wpc.conf.dangerous_perms_write[self.get_otype()][k]: 109 | self.dperms.append(p) 110 | return self.dperms 111 | 112 | def get_perms_dangerous_read(self): 113 | if self.dpermsread == []: 114 | if self.get_type() == "ALLOW": # we ignore DENY aces - mostly correct TODO they're actually checked before ALLOWs. False negatives if user is blocked by DENY 115 | for p in self.get_perms(): 116 | for k in wpc.conf.dangerous_perms_read[self.get_otype()]: 117 | if p in wpc.conf.dangerous_perms_read[self.get_otype()][k]: 118 | self.dpermsread.append(p) 119 | return self.dpermsread 120 | 121 | def as_text(self): 122 | return self.get_type() + " " + self.get_principal().get_fq_name() + ": \n " + "\n ".join(self.get_perms()) 123 | 124 | def as_list(self): 125 | perms = [] 126 | for perm in self.get_perms(): 127 | perms.append([self.get_type(), self.get_principal().get_fq_name(), perm]) 128 | return perms 129 | 130 | def as_tab_delim(self, name): 131 | lines = [] 132 | for perm in self.get_perms(): 133 | lines.append("%s\t%s\t%s\t%s\t%s" % ("RegKey", name, self.get_type(), self.get_principal().get_fq_name(), perm)) 134 | return lines 135 | 136 | def as_tab_delim2(self, name, value): 137 | if value == "": 138 | value = "(Default)" 139 | lines = [] 140 | for perm in self.get_perms(): 141 | lines.append("%s\t%s\t%s\t%s\t%s\t%s" % ("RegKeyVal", name, value, self.get_type(), self.get_principal().get_fq_name(), perm)) 142 | return lines 143 | 144 | def as_tab_delim3(self, name, value, data): 145 | if value == "": 146 | value = "(Default)" 147 | lines = [] 148 | for perm in self.get_perms(): 149 | lines.append("%s\t%s\t%s\t%s\t%s\t%s\t%s" % ("RegKeyValData", name, value, repr(data), self.get_type(), self.get_principal().get_fq_name(), perm)) 150 | return lines 151 | # def dangerous_as_text(self): 152 | # return self.get_type() + " " + self.get_principal().get_fq_name() + ": \n " + "\n ".join(self.get_perms_dangerous()) 153 | -------------------------------------------------------------------------------- /wpc/acelist.py: -------------------------------------------------------------------------------- 1 | from wpc.ace import ace 2 | import ntsecuritycon 3 | import win32security 4 | 5 | 6 | # just a list of ACEs. No owner, group, dacl, sd 7 | # we abstrace this out so we can chain searchs: 8 | # sd.get_aces_untrusted().get_aces_dangerous() 9 | class acelist: 10 | def __init__(self): 11 | self.aces = [] 12 | self.untrusted_acelist = None 13 | pass 14 | 15 | def add(self, ace): 16 | # http://msdn.microsoft.com/en-us/library/aa374919(v=vs.85).aspx 17 | # Ignore ACE if it doesn't apply to this object (i.e. it is instead just inherited by children) 18 | if not ace.get_flags() & ntsecuritycon.INHERIT_ONLY_ACE: 19 | self.aces.append(ace) 20 | 21 | def get_aces(self): 22 | return self.aces 23 | 24 | def get_aces_for(self, principal): 25 | a = acelist() 26 | for ace in self.get_aces(): 27 | if principal.get_sid() == ace.get_sid(): 28 | a.add(ace) 29 | return a 30 | 31 | def get_untrusted(self): 32 | if not self.untrusted_acelist: 33 | self.untrusted_acelist = acelist() 34 | for ace in self.get_aces(): 35 | if not ace.get_principal().is_trusted(): 36 | self.untrusted_acelist.add(ace) 37 | return self.untrusted_acelist 38 | 39 | def get_dangerous_perms(self): 40 | a = acelist() 41 | for ace in self.get_aces(): 42 | if not ace.get_perms_dangerous() == []: 43 | newace = ace.copy() 44 | newace.set_perms(newace.get_perms_dangerous()) 45 | a.add(newace) 46 | return a 47 | 48 | def get_dangerous_perms_read(self): 49 | a = acelist() 50 | for ace in self.get_aces(): 51 | if not ace.get_perms_dangerous_read() == []: 52 | newace = ace.copy() 53 | newace.set_perms(newace.get_perms_dangerous_read()) 54 | a.add(newace) 55 | return a 56 | 57 | def get_aces_with_perms(self, perms): 58 | a = acelist() 59 | for ace in self.get_aces(): 60 | found_perms = [] 61 | for p in perms: 62 | if ace.has_perm(p): 63 | found_perms.append(p) 64 | if not found_perms == []: 65 | newace = ace.copy() 66 | newace.set_perms(found_perms) 67 | a.add(newace) 68 | return a 69 | 70 | def get_aces_except_for(self, principals): 71 | a = acelist 72 | for ace in self.get_aces(): 73 | trusted = 0 74 | for p in principals: 75 | #print "comparing %s with %s" % (p.get_sid(), ace.get_sid()) 76 | if p.get_sid() == ace.get_sid(): 77 | trusted = 1 78 | break 79 | if not trusted: 80 | a.add(ace) 81 | return a 82 | 83 | def as_text(self): 84 | for ace in self.get_aces(): 85 | print ace.as_text() -------------------------------------------------------------------------------- /wpc/audit/__init__.py: -------------------------------------------------------------------------------- 1 | pass 2 | -------------------------------------------------------------------------------- /wpc/audit/auditbase.py: -------------------------------------------------------------------------------- 1 | import wpc.utils 2 | 3 | class auditbase: 4 | def __init__(self, options): 5 | self.options = options 6 | 7 | def run_sub(self, name, condition, sub, *args): 8 | if condition: 9 | if name: 10 | wpc.utils.section(name) 11 | 12 | if self.options.do_errors: 13 | sub(*list(args)) 14 | if name: 15 | wpc.utils.print_major("Checks completed", 1) 16 | else: 17 | try: 18 | sub(*list(args)) 19 | except: 20 | print "[E] Errors occurred but were supressed. Some checks might have been missed. Probably a bug." 21 | finally: 22 | if name: 23 | wpc.utils.print_major("Checks completed", 1) 24 | 25 | 26 | -------------------------------------------------------------------------------- /wpc/audit/dump.py: -------------------------------------------------------------------------------- 1 | from wpc.scheduledtasks import scheduledtasks 2 | from wpc.audit.auditbase import auditbase 3 | import wpc.utils 4 | import win32security 5 | from wpc.shares import shares 6 | from wpc.regkey import regkey 7 | import win32net 8 | import os 9 | from wpc.file import file as File 10 | from wpc.groups import groups 11 | from wpc.processes import processes 12 | from wpc.users import users 13 | from wpc.user import user 14 | from wpc.drives import drives 15 | from wpc.ntobj import ntobj 16 | from wpc.services import drivers, services 17 | import pywintypes 18 | import win32con 19 | import win32service 20 | from wpc.sd import sd 21 | import win32process 22 | import win32ts 23 | 24 | class dump(auditbase): 25 | def __init__(self, options): 26 | self.options = options 27 | 28 | def run(self): 29 | # TODO we don't have to pass options or issues to any subs 30 | self.run_sub("dump_misc_checks", 1, self.dump_misc_checks) 31 | self.run_sub("dump_paths", self.options.do_all or self.options.do_paths, self.dump_paths ) 32 | self.run_sub("dump_all_files", self.options.do_allfiles, self.dump_all_files ) 33 | self.run_sub("dump_eventlogs", self.options.do_all or self.options.do_eventlogs, self.dump_eventlogs ) 34 | self.run_sub("dump_shares", self.options.do_all or self.options.do_shares, self.dump_shares ) 35 | self.run_sub("dump_patches", self.options.do_all or self.options.patchfile, self.dump_patches ) 36 | self.run_sub("dump_loggedin", self.options.do_all or self.options.do_loggedin, self.dump_loggedin ) 37 | self.run_sub("dump_services", self.options.do_all or self.options.do_services, self.dump_services ) 38 | self.run_sub("dump_drivers", self.options.do_all or self.options.do_drivers, self.dump_drivers ) 39 | self.run_sub("dump_drives", self.options.do_all or self.options.do_drives, self.dump_drives ) 40 | self.run_sub("dump_processes", self.options.do_all or self.options.do_processes, self.dump_processes ) 41 | self.run_sub("dump_program_files", self.options.do_all or self.options.do_program_files, self.dump_program_files) 42 | self.run_sub("dump_registry", self.options.do_all or self.options.do_registry, self.dump_registry ) 43 | self.run_sub("dump_scheduled_tasks",self.options.do_all or self.options.do_scheduled_tasks,self.dump_scheduled_tasks) 44 | self.run_sub("dump_reg_keys", self.options.do_all or self.options.do_reg_keys, self.dump_reg_keys ) 45 | self.run_sub("dump_nt_objects", self.options.do_all or self.options.do_nt_objects, self.dump_nt_objects ) 46 | self.run_sub("dump_users", self.options.do_all or self.options.do_users, self.dump_users ) 47 | self.run_sub("dump_groups", self.options.do_all or self.options.do_groups, self.dump_groups ) 48 | self.run_sub("dump_user_modals", self.options.do_all or self.options.get_modals, self.dump_user_modals) 49 | 50 | # ---------------------- Define --dump Subs --------------------------- 51 | def dump_paths(self): 52 | systempath = wpc.utils.get_system_path() 53 | print "System path: %s" % (systempath) 54 | 55 | paths = wpc.utils.get_user_paths() 56 | 57 | for path in paths: 58 | print "Path for user %s: %s" % (path[0].get_fq_name(), path[1]) 59 | 60 | 61 | def dump_scheduled_tasks(self): 62 | for task in scheduledtasks().get_all_tasks(): 63 | print task.as_text() 64 | 65 | def dump_misc_checks(self): 66 | # Check if host is in a domain 67 | in_domain = 0 68 | dc_info = None 69 | try: 70 | dc_info = win32security.DsGetDcName(None, None, None, None, 0) 71 | in_domain = 1 72 | except: 73 | pass 74 | 75 | if in_domain: 76 | print "[+] Host is in domain" 77 | for k in dc_info.keys(): 78 | print "[-] %s => %s" % (k, dc_info[k]) 79 | else: 80 | print "[+] Host is not in domain" 81 | 82 | 83 | def dump_eventlogs(self): 84 | # TODO 85 | print "[E] dump_eventlogs not implemented yet. Sorry." 86 | 87 | 88 | def dump_shares(self): 89 | for s in shares().get_all(): 90 | print s.as_text() 91 | 92 | 93 | def dump_reg_keys(self): 94 | for check, key in wpc.conf.reg_keys.items(): 95 | #print "Checking %s => %s" % (check, key) 96 | key_a = key.split('\\') 97 | value = key_a.pop() 98 | key_s = '\\'.join(key_a) 99 | rk = regkey(key_s) 100 | if rk.is_present: 101 | v = rk.get_value(value) # This value appears as "(Default)" in regedit 102 | print "Check: \"%s\", Key: %s, Value: %s, Data: %s" % (check, key_s, value, v) 103 | 104 | 105 | def dump_patches(self): 106 | # TODO 107 | print "[E] dump_patches not implemented yet. Sorry." 108 | 109 | 110 | def dump_loggedin(self): 111 | resume = 0 112 | print "\n[+] Logged in users:" 113 | try: 114 | while True: 115 | users, _, resume = win32net.NetWkstaUserEnum(wpc.conf.remote_server, 1 , resume , 999999 ) 116 | for user in users: 117 | print "User logged in: Logon Server=\"%s\" Logon Domain=\"%s\" Username=\"%s\"" % (user['logon_server'], user['logon_domain'], user['username']) 118 | if resume == 0: 119 | break 120 | except: 121 | print "[E] Failed" 122 | 123 | 124 | def dump_program_files(self): 125 | # Record info about all directories 126 | include_dirs = 1 127 | 128 | prog_dirs = [] 129 | if os.getenv('ProgramFiles'): 130 | prog_dirs.append(os.environ['ProgramFiles']) 131 | 132 | if os.getenv('ProgramFiles(x86)'): 133 | prog_dirs.append(os.environ['ProgramFiles(x86)']) 134 | 135 | for directory in prog_dirs: 136 | # Walk program files directories looking for executables 137 | for filename in wpc.utils.dirwalk(directory, wpc.conf.executable_file_extensions, include_dirs): 138 | f = File(filename) 139 | print f.as_text() 140 | 141 | 142 | def dump_services(self): 143 | for s in services().get_services(): 144 | if s: 145 | print s.as_text() 146 | else: 147 | print "[W] Failed to get info about a service. Skipping." 148 | 149 | 150 | def dump_drivers(self): 151 | for d in drivers().get_services(): 152 | print d.as_text() 153 | 154 | 155 | def dump_drives(self): 156 | for d in drives().get_fixed_drives(): 157 | print "%s: (%s)" % (d.get_name(), d.get_fs()) 158 | 159 | 160 | def dump_processes(self): 161 | for p in processes().get_all(): 162 | print p.as_text() 163 | 164 | # When listing DLLs for a process we need to see the filesystem like they do 165 | if p.is_wow64(): 166 | wpc.utils.enable_wow64() 167 | 168 | if p.get_exe(): 169 | print "Security Descriptor for Exe File %s" % p.get_exe().get_name() 170 | if p.get_exe().get_sd(): 171 | print p.get_exe().get_sd().as_text() 172 | else: 173 | print "[unknown]" 174 | 175 | for dll in p.get_dlls(): 176 | print "\nSecurity Descriptor for DLL File %s" % dll.get_name() 177 | sd = dll.get_sd() 178 | if sd: 179 | print sd.as_text() 180 | 181 | if p.is_wow64(): 182 | wpc.utils.disable_wow64() 183 | 184 | 185 | def dump_users(self, get_privs = 0): 186 | print "[+] Dumping user list:" 187 | userlist = users() 188 | for u in userlist.get_all(): 189 | print u.get_fq_name() 190 | 191 | if get_privs: 192 | print "\n\t[+] Privileges of this user:" 193 | for priv in u.get_privileges(): 194 | print "\t%s" % priv 195 | 196 | print "\n\t[+] Privileges of this user + the groups it is in:" 197 | for p in u.get_effective_privileges(): 198 | print "\t%s" % p 199 | print 200 | 201 | 202 | def dump_user_modals(self): 203 | d1 = d2 = d3 = d4 = {} 204 | try: 205 | d1 = win32net.NetUserModalsGet(wpc.conf.remote_server, 0) 206 | d2 = win32net.NetUserModalsGet(wpc.conf.remote_server, 1) 207 | d3 = win32net.NetUserModalsGet(wpc.conf.remote_server, 2) 208 | d4 = win32net.NetUserModalsGet(wpc.conf.remote_server, 3) 209 | except pywintypes.error as e: 210 | print "[E] %s: %s" % (e[1], e[2]) 211 | 212 | for d in (d1, d2, d3, d4): 213 | for k in d.keys(): 214 | print "%s: %s" % (k, d[k]) 215 | 216 | def dump_groups(self, get_privs = 0): 217 | print "[+] Dumping group list:" 218 | grouplist = groups() 219 | for g in grouplist.get_all(): 220 | group_name = g.get_fq_name() 221 | 222 | for m in g.get_members(): 223 | print "%s has member: %s" % (group_name, m.get_fq_name()) 224 | 225 | if get_privs: 226 | for priv in g.get_privileges(): 227 | print "%s has privilege: %s" % (group_name, priv) 228 | 229 | # TODO 230 | # print "\n\t[+] Privileges of this group + the groups it is in:" 231 | # for p in g.get_effective_privileges(): 232 | # print "\t%s" % p 233 | 234 | 235 | def dump_registry(self): 236 | for r in regkey('HKLM').get_all_subkeys(): 237 | print r.as_text() 238 | 239 | 240 | def dump_nt_objects(self): 241 | 242 | # 243 | # Windows stations and Desktops - TODO make is more OO: objects for windowstations and desktops. 244 | # 245 | win32con.WINSTA_ALL_ACCESS = 0x0000037f 246 | 247 | print 248 | print "[-] Sessions" 249 | print 250 | for session in win32ts.WTSEnumerateSessions(win32ts.WTS_CURRENT_SERVER_HANDLE, 1, 0): 251 | print "SessionId: %s" % session['SessionId'] 252 | print "\tWinStationName: %s" % session['WinStationName'] 253 | print "\tState: %s" % session['State'] 254 | print 255 | 256 | session = win32ts.ProcessIdToSessionId(win32process.GetCurrentProcessId()) 257 | print 258 | print "[-] Winstations in session %s" % session 259 | print 260 | for w in win32service.EnumWindowStations(): 261 | print "winstation: %s" % w 262 | print 263 | 264 | for w in win32service.EnumWindowStations(): 265 | print 266 | print "[-] Session %s, Winstation '%s'" % (session, w) 267 | print 268 | 269 | # Get SD 270 | try: 271 | h = 0 272 | h = win32service.OpenWindowStation(w, False, win32con.READ_CONTROL) 273 | s = win32security.GetKernelObjectSecurity(h, win32security.OWNER_SECURITY_INFORMATION | win32security.GROUP_SECURITY_INFORMATION | win32security.DACL_SECURITY_INFORMATION) 274 | s = sd('winstation', s) 275 | print s.as_text() 276 | except pywintypes.error,details: 277 | print "[E] Can't get READ_CONTROL winstation handle: %s" % details 278 | 279 | # Get Desktops 280 | try: 281 | h = 0 282 | h = win32service.OpenWindowStation(w, False, win32con.WINSTA_ENUMDESKTOPS) 283 | print "[-] Session %s, Winstation '%s' has these desktops:" % (session, w) 284 | for d in h.EnumDesktops(): 285 | print "\t%s" % d 286 | print 287 | except pywintypes.error,details: 288 | print "[E] Can't get WINSTA_ENUMDESKTOPS winstation handle: %s" % details 289 | if h: 290 | h.SetProcessWindowStation() 291 | for d in h.EnumDesktops(): 292 | print "[-] Session %s, Winstation '%s', Desktop '%s'" % (session, w, d) 293 | try: 294 | hd = win32service.OpenDesktop(d, 0, False, win32con.READ_CONTROL) 295 | s = win32security.GetKernelObjectSecurity(hd, win32security.OWNER_SECURITY_INFORMATION | win32security.GROUP_SECURITY_INFORMATION | win32security.DACL_SECURITY_INFORMATION) 296 | s = sd('desktop', s) 297 | print s.as_text() 298 | except pywintypes.error,details: 299 | print "[E] Can't get READ_CONTROL desktop handle: %s" % details 300 | print 301 | 302 | # 303 | # Objects 304 | # 305 | print 306 | print "[-] Objects" 307 | print 308 | root = ntobj("\\") 309 | for child in root.get_all_child_objects(): 310 | print child.as_text() 311 | if (child.get_type() == "Semaphore" or child.get_type() == "Event" or child.get_type() == "Mutant" or child.get_type() == "Timer" or child.get_type() == "Section" or child.get_type() == "Device" or child.get_type() == "SymbolicLink" or child.get_type() == "Key" or child.get_type() == "Directory") and child.get_sd(): 312 | print child.get_sd().as_text() 313 | else: 314 | print "Skipping unknown object type: %s" % child.get_type() 315 | print 316 | 317 | # Type - can't open 318 | # Device - can open, has sd 319 | # SymbolicLink - can open, has sd 320 | 321 | # TODO is this redundant now we have --dumptab? 322 | def dump_all_files(self): 323 | # Record info about all directories 324 | include_dirs = 1 325 | 326 | # TODO other drives too 327 | 328 | prog_dirs = [] 329 | prog_dirs.append('c:\\') 330 | 331 | count = 0 332 | for dir in prog_dirs: 333 | # Walk program files directories looking for executables 334 | for filename in wpc.utils.dirwalk(dir, '*', include_dirs): 335 | f = File(filename) 336 | #print "[D] Processing %s" % f.get_name() 337 | # TODO check file owner, parent paths, etc. Maybe use is_replaceable instead? 338 | aces = f.get_dangerous_aces() 339 | count = count + 1 340 | for ace in aces: 341 | for p in ace.get_perms(): 342 | print "%s\t%s\t%s\t%s\t%s" % (f.get_type(), f.get_name(), ace.get_type(), ace.get_principal().get_fq_name(), p) 343 | 344 | 345 | -------------------------------------------------------------------------------- /wpc/audit/dumptab.py: -------------------------------------------------------------------------------- 1 | from wpc.scheduledtasks import scheduledtasks 2 | from wpc.audit.auditbase import auditbase 3 | from wpc.file import file as File 4 | from wpc.groups import groups 5 | from wpc.processes import processes 6 | from wpc.process import process 7 | from wpc.regkey import regkey 8 | from wpc.services import drivers, services 9 | from wpc.users import users 10 | from wpc.user import user 11 | from wpc.shares import shares 12 | from wpc.drives import drives 13 | from wpc.ntobj import ntobj 14 | import pywintypes 15 | import win32net 16 | import os 17 | import wpc.conf 18 | import wpc.utils 19 | import win32security 20 | 21 | class dumptab(auditbase): 22 | def __init__(self, options, report): 23 | self.options = options 24 | self.report = report 25 | 26 | def run(self): 27 | self.run_sub("", 1, self.dumptab_misc_checks) 28 | self.run_sub("", self.options.do_all or self.options.do_paths, self.dumptab_paths) 29 | self.run_sub("", self.options.do_allfiles, self.dumptab_all_files) 30 | self.run_sub("", self.options.do_all or self.options.do_eventlogs, self.dumptab_eventlogs) 31 | self.run_sub("", self.options.do_all or self.options.do_shares, self.dumptab_shares) 32 | self.run_sub("", self.options.do_all or self.options.patchfile, self.dumptab_patches) 33 | self.run_sub("", self.options.do_all or self.options.do_loggedin, self.dumptab_loggedin) 34 | self.run_sub("", self.options.do_all or self.options.do_services, self.dumptab_services) 35 | self.run_sub("", self.options.do_all or self.options.do_drivers, self.dumptab_drivers) 36 | self.run_sub("", self.options.do_all or self.options.do_drives, self.dumptab_drives) 37 | self.run_sub("", self.options.do_all or self.options.do_processes, self.dumptab_processes) 38 | self.run_sub("", self.options.do_all or self.options.do_program_files, self.dumptab_program_files) 39 | self.run_sub("", self.options.do_all or self.options.do_registry, self.dumptab_registry) 40 | self.run_sub("", self.options.do_all or self.options.do_scheduled_tasks, self.dumptab_scheduled_tasks) 41 | self.run_sub("", self.options.do_all or self.options.do_reg_keys, self.dumptab_reg_keys) 42 | self.run_sub("", self.options.do_all or self.options.do_installed_software, self.dumptab_installed_software) 43 | self.run_sub("", self.options.do_all or self.options.do_nt_objects, self.dumptab_nt_objects) 44 | self.run_sub("", self.options.do_all or self.options.do_users, self.dumptab_users) 45 | self.run_sub("", self.options.do_all or self.options.do_groups, self.dumptab_groups) 46 | self.run_sub("", self.options.do_all or self.options.get_modals, self.dumptab_user_modals) 47 | 48 | # ---------------------- Define --dumptab Subs --------------------------- 49 | def dumptab_paths(self): 50 | paths = wpc.utils.get_user_paths() 51 | 52 | for path in paths: 53 | print wpc.utils.tab_line("info", "user_path", path[0].get_fq_name(), path[1]) 54 | 55 | systempath = wpc.utils.get_system_path() 56 | print wpc.utils.tab_line("info", "system_path", systempath) 57 | 58 | 59 | def dumptab_scheduled_tasks(self): 60 | for task in scheduledtasks().get_all_tasks(): 61 | print task.as_tab() 62 | 63 | 64 | def dumptab_all_files(self): 65 | # Record info about all directories 66 | include_dirs = 1 67 | 68 | # Identify all NTFS drives 69 | prog_dirs = [] 70 | for d in drives().get_fixed_drives(): 71 | print wpc.utils.tab_line("info", "drive", d.get_name(), d.get_fs()) 72 | if d.get_fs() == 'NTFS': 73 | prog_dirs.append(d.get_name()) 74 | 75 | # Walk the directory tree of each NTFS drive 76 | for directory in prog_dirs: 77 | for filename in wpc.utils.dirwalk(directory, '*', include_dirs): 78 | f = File(filename) 79 | print f.as_tab() 80 | 81 | 82 | def dumptab_eventlogs(self): 83 | pass 84 | 85 | 86 | def dumptab_misc_checks(self): 87 | # Check if host is in a domain 88 | in_domain = 0 89 | dc_info = None 90 | try: 91 | dc_info = win32security.DsGetDcName(None, None, None, None, 0) 92 | in_domain = 1 93 | except: 94 | pass 95 | 96 | # DC information if available 97 | if in_domain: 98 | print wpc.utils.tab_line("info", "in_domain", "yes") 99 | for k in dc_info.keys(): 100 | print wpc.utils.tab_line("info", "dc", k, dc_info[k]) 101 | else: 102 | print wpc.utils.tab_line("info", "in_domain", "no") 103 | 104 | # misc information that appears in HTML report 105 | for i in ['hostname', 'datetime', 'version', 'user', 'domain', 'ipaddress', 'os', 'os_version']: 106 | print wpc.utils.tab_line("info", i, self.report.get_info_item(i)) 107 | 108 | 109 | def dumptab_shares(self): 110 | for s in shares().get_all(): 111 | print s.as_tab() 112 | 113 | 114 | def dumptab_patches(self): 115 | pass 116 | 117 | 118 | def dumptab_installed_software(self): 119 | uninstall = regkey('HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Uninstall') 120 | if uninstall.is_present(): 121 | for subkey in uninstall.get_subkeys(): 122 | name = subkey.get_value("DisplayName") 123 | publisher = subkey.get_value("Publisher") 124 | version = subkey.get_value("DisplayVersion") 125 | date = subkey.get_value("InstallDate") 126 | if name: 127 | print wpc.utils.tab_line("info", "installed_software", name, publisher, version, date) 128 | 129 | if process(os.getpid()).is_wow64(): 130 | print '[+] Checking installed software (WoW64 enabled)' 131 | uninstall = regkey('HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Uninstall', view=64) 132 | if uninstall.is_present(): 133 | for subkey in uninstall.get_subkeys(): 134 | name = subkey.get_value("DisplayName") 135 | publisher = subkey.get_value("Publisher") 136 | version = subkey.get_value("DisplayVersion") 137 | date = subkey.get_value("InstallDate") 138 | if name: 139 | print wpc.utils.tab_line("info", "installed_software", name, publisher, version, date) 140 | 141 | def dumptab_loggedin(self): 142 | resume = 0 143 | try: 144 | while True: 145 | users, _, resume = win32net.NetWkstaUserEnum(wpc.conf.remote_server, 1 , resume , 999999 ) 146 | for user in users: 147 | u = "%s\\%s" % (user['logon_domain'], user['username']) 148 | print wpc.utils.tab_line("info", "logged_in_user", u, user['logon_server']) 149 | if resume == 0: 150 | break 151 | except: 152 | print "[E] Failed" 153 | 154 | def dumptab_services(self): 155 | for s in services().get_services(): 156 | if s: 157 | print s.as_tab() 158 | 159 | 160 | def dumptab_drivers(self): 161 | for d in drivers().get_services(): 162 | print d.as_tab() 163 | 164 | 165 | def dumptab_drives(self): 166 | for d in drives().get_fixed_drives(): 167 | print wpc.utils.tab_line("info", "drive", d.get_name(), d.get_fs()) 168 | 169 | 170 | def dumptab_processes(self): 171 | for p in processes().get_all(): 172 | print p.as_tab() 173 | 174 | 175 | def dumptab_program_files(self): 176 | # Record info about all directories 177 | include_dirs = 1 178 | 179 | prog_dirs = [] 180 | if os.getenv('ProgramFiles'): 181 | prog_dirs.append(os.environ['ProgramFiles']) 182 | 183 | if os.getenv('ProgramFiles(x86)'): 184 | prog_dirs.append(os.environ['ProgramFiles(x86)']) 185 | 186 | for directory in prog_dirs: 187 | # Walk program files directories looking for executables 188 | for filename in wpc.utils.dirwalk(directory, wpc.conf.executable_file_extensions, include_dirs): 189 | f = File(filename) 190 | print f.as_tab() 191 | 192 | 193 | def dumptab_registry(self): 194 | for r in regkey('HKLM').get_all_subkeys(): 195 | print r.as_tab() 196 | 197 | 198 | def dumptab_reg_keys(self): 199 | pass 200 | 201 | 202 | def dumptab_nt_objects(self): 203 | for child in ntobj("\\").get_all_child_objects(): 204 | print child.as_tab() 205 | 206 | 207 | def dumptab_users(self): 208 | userlist = users() 209 | for u in userlist.get_all(): 210 | print wpc.utils.tab_line("info", "user", u.get_fq_name(), u.get_sid_string()) 211 | 212 | for p in u.get_effective_privileges(): 213 | print wpc.utils.tab_line("info", "user_effective_privilege", u.get_fq_name(), p) 214 | 215 | for priv in u.get_privileges(): 216 | print wpc.utils.tab_line("info", "user_privilege", u.get_fq_name(), priv) 217 | 218 | 219 | def dumptab_groups(self): 220 | grouplist = groups() 221 | for g in grouplist.get_all(): 222 | print wpc.utils.tab_line("info", "group", g.get_fq_name(), g.get_sid_string()) 223 | for m in g.get_members(): 224 | print wpc.utils.tab_line("info", "group_member", g.get_fq_name(), m.get_fq_name()) 225 | 226 | for priv in g.get_privileges(): 227 | print wpc.utils.tab_line("info", "group_privilege", g.get_fq_name(), priv) 228 | 229 | 230 | def dumptab_user_modals(self): 231 | d1 = d2 = d3 = d4 = {} 232 | try: 233 | d1 = win32net.NetUserModalsGet(wpc.conf.remote_server, 0) 234 | d2 = win32net.NetUserModalsGet(wpc.conf.remote_server, 1) 235 | d3 = win32net.NetUserModalsGet(wpc.conf.remote_server, 2) 236 | d4 = win32net.NetUserModalsGet(wpc.conf.remote_server, 3) 237 | except pywintypes.error as e: 238 | print "[E] %s: %s" % (e[1], e[2]) 239 | 240 | for d in (d1, d2, d3, d4): 241 | for k in d.keys(): 242 | print wpc.utils.tab_line("info", "user_modals", k, d[k]) 243 | 244 | 245 | -------------------------------------------------------------------------------- /wpc/cache.py: -------------------------------------------------------------------------------- 1 | from wpc.sd import sd 2 | import win32net 3 | import win32netcon 4 | import win32security 5 | import wpc.file 6 | #from wpc.file import file as wpcfile 7 | 8 | 9 | # Basically a huge hash of all lookups 10 | # 11 | # There should be only one instance of the cache which is started when the script is initialised 12 | # All classes are hard-coded to use this instance of "cache" 13 | # 14 | # wpc.cache # single global instance of this class 15 | # 16 | # Some attributes of "cache" determine how it behaves 17 | # wpc.conf.cache... 18 | class cache: 19 | def __init__(self): 20 | self.namefromsid = {} 21 | self.sidfromname = {} 22 | self.stringfromsid = {} 23 | self.sidingroup = {} 24 | self.files = {} 25 | self.regkeys = {} 26 | self.misses = {} 27 | self.hits = {} 28 | self.policyhandlefromserverrights = {} 29 | self.rightsfromhandlesid = {} 30 | self.namefromserveruser = {} 31 | self.hits['files'] = 0 32 | self.misses['files'] = 0 33 | self.hits['regkeys'] = 0 34 | self.misses['regkeys'] = 0 35 | self.hits['sd'] = 0 36 | self.misses['sd'] = 0 37 | self.hits['LookupAccountSid'] = 0 38 | self.misses['LookupAccountSid'] = 0 39 | self.hits['LookupAccountName'] = 0 40 | self.misses['LookupAccountName'] = 0 41 | self.hits['is_in_group'] = 0 42 | self.misses['is_in_group'] = 0 43 | 44 | def print_stats(self): 45 | for k in self.hits.keys(): 46 | print "Hits for %s: %s" % (k, self.get_hits(k)) 47 | print "Misses for %s: %s" % (k, self.get_misses(k)) 48 | 49 | def sd(self, type, name): 50 | # TODO caching code here 51 | return sd(type, name) 52 | 53 | def File(self, name): 54 | f = None # might save 1 x dict lookup 55 | if name in self.files.keys(): 56 | #print "[D] Cache hitx for: " + self.files[name].get_name() 57 | self.hit('files') 58 | return self.files[name] 59 | else: 60 | self.miss('files') 61 | f = wpc.file.file(name) 62 | self.files[name] = f 63 | return f 64 | 65 | def regkey(self, name): 66 | f = None # might save 1 x dict lookup 67 | if name in self.regkeys.keys(): 68 | #print "[D] Cache hitx for: " + self.files[name].get_name() 69 | self.hit('regkeys') 70 | return self.regkeys[name] 71 | else: 72 | self.miss('regkeys') 73 | f = wpc.regkey.regkey(name) 74 | self.regkeys[name] = f 75 | return f 76 | 77 | def LsaOpenPolicy(self, server, rights): 78 | keystring = "%s%%%s" %(server, rights) 79 | if not keystring in self.policyhandlefromserverrights.keys(): 80 | self.policyhandlefromserverrights[keystring] = win32security.LsaOpenPolicy(wpc.conf.remote_server, win32security.POLICY_VIEW_LOCAL_INFORMATION | win32security.POLICY_LOOKUP_NAMES) 81 | return self.policyhandlefromserverrights[keystring] 82 | 83 | def LsaEnumerateAccountRights(self, handle, sid): 84 | keystring = "%s%%%s" %(handle, sid) 85 | if not keystring in self.rightsfromhandlesid.keys(): 86 | try: 87 | self.rightsfromhandlesid[keystring] = win32security.LsaEnumerateAccountRights(handle, sid) 88 | except: 89 | self.rightsfromhandlesid[keystring] = "" 90 | 91 | return self.rightsfromhandlesid[keystring] 92 | 93 | def LookupAccountSid(self, server, s): 94 | sid = win32security.ConvertSidToStringSid(s) 95 | if not server in self.namefromsid.keys(): 96 | self.namefromsid[server] = {} 97 | if not sid in self.namefromsid[server].keys(): 98 | try: 99 | self.namefromsid[server][sid] = win32security.LookupAccountSid(server, s) 100 | except: 101 | self.namefromsid[server][sid] = (win32security.ConvertSidToStringSid(s), "[unknown]", 8) 102 | self.miss('LookupAccountSid') 103 | else: 104 | self.hit('LookupAccountSid') 105 | 106 | return self.namefromsid[server][sid] 107 | 108 | def LookupAccountName(self, server, name): 109 | if not server in self.sidfromname.keys(): 110 | self.sidfromname[server] = {} 111 | if not name in self.sidfromname[server].keys(): 112 | try: 113 | self.sidfromname[server][name] = win32security.LookupAccountName(server, name) 114 | except: 115 | self.sidfromname[server][name] = None 116 | self.miss('LookupAccountName') 117 | else: 118 | self.hit('LookupAccountName') 119 | 120 | return self.sidfromname[server][name] 121 | 122 | def hit(self, name): 123 | self.hits[name] = self.hits[name] + 1 124 | 125 | def miss(self, name): 126 | self.misses[name] = self.misses[name] + 1 127 | 128 | def get_hits(self, name): 129 | return self.hits[name] 130 | 131 | def get_misses(self, name): 132 | return self.misses[name] 133 | 134 | def is_in_group(self, p, group): 135 | # print "cache.is_in_group called" 136 | #sid = win32security.ConvertSidToStringSid(s) 137 | # print "[D] 1" 138 | sid = p.get_sid_string() 139 | if not sid in self.sidingroup.keys(): 140 | self.sidingroup[sid] = {} 141 | # print "[D] 2" 142 | # print "is_in_group group.get_sid_string(): %s" % group.get_sid_string() 143 | # print "is_in_group sid: %s" % sid 144 | # print "members" 145 | # print map(lambda x: x.get_sid_string(), group.get_members()) 146 | if not group.get_sid_string() in self.sidingroup[sid].keys(): 147 | self.sidingroup[sid][group.get_sid_string()] = 0 148 | self.miss('is_in_group') 149 | #print "Miss for is_in_group" 150 | if p.get_sid_string() in map(lambda x: x.get_sid_string(), group.get_members()): 151 | self.sidingroup[sid][group.get_sid_string()] = 1 152 | # print "[D] 3" 153 | else: 154 | #print "Hit for is_in_group" 155 | self.hit('is_in_group') 156 | # print "Returning: %s" % self.sidingroup[sid][group.get_sid_string()] 157 | return self.sidingroup[sid][group.get_sid_string()] 158 | 159 | def NetGroupGetUsers(self, server, name, level): 160 | keepgoing = 1 161 | resume = 0 162 | members = [] 163 | while keepgoing: 164 | try: 165 | m, total, resume = win32net.NetGroupGetUsers(server, name, level, resume, win32netcon.MAX_PREFERRED_LENGTH) 166 | except: 167 | return [] 168 | 169 | for member in m: 170 | members.append(member) 171 | 172 | if not resume: 173 | keepgoing = 0 174 | return members 175 | 176 | def NetLocalGroupGetMembers(self, server, name, level): 177 | keepgoing = 1 178 | resume = 0 179 | members = [] 180 | while keepgoing: 181 | try: 182 | m, total, resume = win32net.NetLocalGroupGetMembers(server, name, level, resume, win32netcon.MAX_PREFERRED_LENGTH) 183 | except: 184 | return [] 185 | 186 | for member in m: 187 | members.append(member) 188 | 189 | if not resume: 190 | keepgoing = 0 191 | return members -------------------------------------------------------------------------------- /wpc/drive.py: -------------------------------------------------------------------------------- 1 | import win32api 2 | import win32con 3 | import win32file 4 | 5 | 6 | # NB: Only works for fixed drives - or you get "device not ready" error 7 | class drive(): 8 | def __init__(self, drivename): 9 | self.filesystem = None 10 | self.drivetype = None 11 | self.drivename = drivename 12 | self.driveinfo = win32api.GetVolumeInformation(drivename) 13 | 14 | def get_name(self): 15 | return self.drivename 16 | 17 | def get_fs(self): 18 | if not self.filesystem: 19 | self.filesystem = self.driveinfo[4] 20 | 21 | return self.filesystem 22 | 23 | def get_type(self): 24 | if not self.drivetype: 25 | self.drivetype = win32file.GetDriveType(self.drivename) 26 | 27 | return self.drivetype 28 | 29 | def is_fixed_drive(self): 30 | if self.get_type() == win32con.DRIVE_FIXED: 31 | return 1 32 | return 0 33 | -------------------------------------------------------------------------------- /wpc/drives.py: -------------------------------------------------------------------------------- 1 | from wpc.drive import drive 2 | import win32api 3 | import win32con 4 | import win32file 5 | 6 | 7 | class drives(): 8 | def get_fixed_drives(self): 9 | for d in win32api.GetLogicalDriveStrings().split("\x00")[0:-1]: 10 | if win32file.GetDriveType(d) == win32con.DRIVE_FIXED or win32file.GetDriveType(d) == 4: 11 | yield drive(d) 12 | -------------------------------------------------------------------------------- /wpc/exploit.py: -------------------------------------------------------------------------------- 1 | # Class to store information about a known exploit. 2 | # This code doesn't actually exploit anything. 3 | 4 | 5 | # These have members 6 | class exploit(): 7 | def __init__(self): 8 | self.title = None 9 | self.description = None 10 | self.urls = [] 11 | self.info = {} 12 | self.refnos = {} 13 | 14 | def set_title(self, title): 15 | self.title = title 16 | 17 | def get_title(self): 18 | return self.title 19 | 20 | def get_msno(self): 21 | if 'MS Bulletin' in self.refnos: 22 | return self.refnos['MS Bulletin'] 23 | return None 24 | 25 | def get_description(self): 26 | return self.description 27 | 28 | def add_refno(self, reftype, ref): 29 | self.refnos[reftype] = ref 30 | 31 | def set_info(self, inftype, info): 32 | self.info[inftype] = info 33 | 34 | def get_info(self, inftype): 35 | if inftype in self.info.keys(): 36 | return self.info[inftype] 37 | return None 38 | 39 | def add_url(self, url): 40 | # TODO uniq 41 | self.urls.append(url) 42 | 43 | def as_string(self): 44 | print "Title: %s" % self.title 45 | if self.description: 46 | print "Description: %s" % self.description 47 | if self.urls: 48 | print "URLs: %s" % " \n".join(self.urls) 49 | for k in self.info.keys(): 50 | print "%s: %s" % (k, self.info[k]) 51 | for k in self.refnos.keys(): 52 | print "%s: %s" % (k, self.refnos[k]) 53 | print 54 | -------------------------------------------------------------------------------- /wpc/file.py: -------------------------------------------------------------------------------- 1 | from wpc.report.fileAcl import fileAcl 2 | from wpc.sd import sd 3 | import os 4 | import win32security 5 | import wpc.conf 6 | 7 | 8 | # files or directories 9 | class file: 10 | def __init__(self, name): 11 | # print "[D] Created file obj for " + name 12 | self.name = str(name).replace("\x00", "") 13 | self.type = None 14 | self.parent_dir = None 15 | self.replaceable_set = None 16 | self.replaceable = None 17 | self.exist = None 18 | self.existsset = 0 19 | # TODO could we defer this check? 20 | if os.path.isdir(self.name): 21 | self.type = 'dir' 22 | if wpc.utils.is_reparse_point(self.name): 23 | self.type = 'reparse_point' 24 | # print "[D] reparse point: %s" % self.name 25 | else: 26 | self.type = 'file' 27 | self.sd = None 28 | 29 | # def clearmem(self): 30 | # self.name = None 31 | # self.type = None 32 | # self.parent_dir = None 33 | # self.replaceable_set = None 34 | # self.replaceable = None 35 | # self.exist = None 36 | # self.sd = None 37 | 38 | def as_text(self): 39 | s = "Filename: " + self.get_name() + "\n" 40 | s += self.get_sd().as_text() 41 | return s 42 | 43 | def dump(self): 44 | print self.as_text() 45 | 46 | def get_name(self): 47 | return self.name 48 | 49 | def exists(self): 50 | if not self.existsset: 51 | try: 52 | self.exist = os.path.exists(self.get_name()) 53 | except: 54 | self.exist = 0 55 | self.existsset = 1 56 | return self.exist 57 | 58 | def is_dir(self): 59 | if self.type == 'dir': 60 | return 1 61 | else: 62 | return 0 63 | 64 | def get_type(self): 65 | return self.type 66 | 67 | def is_file(self): 68 | if self.type == 'file': 69 | return 1 70 | else: 71 | return 0 72 | 73 | def get_file_acl_for_perms(self, perms): 74 | if self.get_sd(): 75 | al = self.get_sd().get_acelist().get_untrusted().get_aces_with_perms(perms).get_aces() 76 | if al == []: 77 | return None 78 | else: 79 | return fileAcl(self.get_name(), al) 80 | 81 | def get_dangerous_aces(self): 82 | try: 83 | #print "[D] File: " + self.get_name() 84 | #print "[D] ACE: " 85 | #for a in self.get_sd().get_acelist().get_dangerous_perms().get_aces(): 86 | # print a.as_text() 87 | return self.get_sd().get_acelist().get_untrusted().get_dangerous_perms().get_aces() 88 | except: 89 | return [] 90 | 91 | def get_dangerous_aces_read(self): 92 | try: 93 | #print "[D] File: " + self.get_name() 94 | #print "[D] ACE: " 95 | #for a in self.get_sd().get_acelist().get_dangerous_perms().get_aces(): 96 | # print a.as_text() 97 | return self.get_sd().get_acelist().get_untrusted().get_dangerous_perms_read().get_aces() 98 | except: 99 | return [] 100 | 101 | # Can an untrusted user replace this file/dir? TODO unused 102 | def is_replaceable(self): 103 | if not self.exists(): 104 | print "[W] is_replaceable called for non-existent file %s" % self.get_name() 105 | return 0 106 | 107 | # There are a few things that could cause a file/dir to be replacable. Firstly let's define "replaceable": 108 | # Replaceable file: Contents can be replaced by untrusted user. Boils down to either write access, or being able to delete then re-add 109 | # Replaceable dir: Untrusted user can deleting anything within and re-add 110 | # 111 | # The code below is a bit subtle because it's recursive. We're checking these conditions: 112 | # 113 | # 1. File/dir is owned by an untrusted user 114 | # 2. File/dir allows FILE_WRITE_DAC for an untrusted user 115 | # 3. File/dir allows FILE_WRITE_OWNER for an untrusted user 116 | # 4. File allows FILE_WRITE_DATA for an untrusted user 117 | # 5. File allows DELETE and parent dir allows FILE_ADD_FILE for an untrusted user 118 | # 6. Parent dir allows FILE_DELETE_CHILD and FILE_ADD_FILE for an untrusted user 119 | # 7. Parent of directory or grandparent of file allows FILE_DELETE_CHILD and FILE_ADD_SUBFOLDER by untrusted user 120 | # 8. Parent dir allows DELETE by untrusted user and its parent allows FILE_ADD_SUBFOLDER 121 | # 9. Parent dir (or any parent thereof) allows WRITE_DAC for an untrusted user 122 | # 10. Parent dir (or any parent thereof) allows WRITE_OWNER for an untrusted user 123 | # 11. Parent dir (or any parent thereof) is owned by an untrusted user 124 | 125 | # Return cached result if we have it 126 | if self.replaceable_set: 127 | # print "[D] Cache hit for " + self.get_name() 128 | return self.replaceable 129 | 130 | # Checks applicable to both files and directories 131 | if self.get_sd(): 132 | # 1. File is owned by an untrusted user 133 | # 11. Parent dir (or any parent thereof) is owned by an untrusted user 134 | # Also see below for a recursive check of parent directories 135 | if self.get_sd().owner_is_untrusted(): 136 | self.replaceable_set = 1 137 | self.replaceable = 1 138 | return 1 139 | 140 | # 2. File allows FILE_WRITE_DAC for an untrusted user 141 | # 3. File allows FILE_WRITE_OWNER for an untrusted user 142 | # 9. Parent dir (or any parent thereof) allows WRITE_DAC for an untrusted user 143 | # 10. Parent dir (or any parent thereof) allows WRITE_OWNER for an untrusted user 144 | # Also see below for a recursive check of parent directories 145 | if not self.get_sd().get_acelist().get_untrusted().get_aces_with_perms(["FILE_WRITE_DAC", "FILE_WRITE_OWNER"]).get_aces() == []: 146 | self.replaceable_set = 1 147 | self.replaceable = 1 148 | return 1 149 | 150 | # Checks applicable to only files 151 | if self.type == 'file': 152 | if self.get_sd(): 153 | # 4. File allows FILE_WRITE_DATA for an untrusted user 154 | if not self.get_sd().get_acelist().get_untrusted().get_aces_with_perms(["FILE_WRITE_DATA"]).get_aces() == []: 155 | self.replaceable_set = 1 156 | self.replaceable = 1 157 | return 1 158 | 159 | # 5. File allows DELETE and parent dir allows FILE_ADD_FILE for an untrusted user 160 | if not self.get_sd().get_acelist().get_untrusted().get_aces_with_perms(["DELETE"]).get_aces() == []: 161 | if self.get_parent_dir().get_sd(): 162 | if not self.get_parent_dir().get_sd().get_acelist().get_untrusted().get_aces_with_perms(["FILE_ADD_FILE"]).get_aces() == []: 163 | self.replaceable_set = 1 164 | self.replaceable = 1 165 | return 1 166 | 167 | # 6. Parent dir allows FILE_DELETE_CHILD and FILE_ADD_FILE for an untrusted user 168 | # NB: We don't require that a single ACE contains both perms. If untrusted user x has FILE_DELETE_CHILD and untrusted user y has perm FILE_ADD_FILE, 169 | # this is still insecure. 170 | if self.get_parent_dir().get_sd(): 171 | if not self.get_parent_dir().get_sd().get_acelist().get_untrusted().get_aces_with_perms(["FILE_ADD_FILE"]).get_aces() == [] and not self.get_parent_dir().get_sd().get_acelist().get_untrusted().get_aces_with_perms(["FILE_DELETE_CHILD"]).get_aces() == []: 172 | self.replaceable_set = 1 173 | self.replaceable = 1 174 | return 1 175 | 176 | if self.type == 'dir': 177 | # 7. Parent of directory or grandparent of file allows FILE_DELETE_CHILD and FILE_ADD_SUBFOLDER by untrusted user 178 | # 8. Parent dir allows DELETE by untrusted user and its parent allows FILE_ADD_SUBFOLDER 179 | if self.get_sd(): 180 | if not self.get_sd().get_acelist().get_untrusted().get_aces_with_perms(["DELETE"]).get_aces() == []: 181 | if self.get_parent_dir() and self.get_parent_dir().get_sd(): 182 | if not self.get_parent_dir().get_sd().get_acelist().get_untrusted().get_aces_with_perms(["FILE_ADD_SUBFOLDER"]).get_aces() == []: 183 | self.replaceable_set = 1 184 | self.replaceable = 1 185 | return 1 186 | 187 | if self.get_parent_dir() and self.get_parent_dir().get_sd(): 188 | if not self.get_parent_dir().get_sd().get_acelist().get_untrusted().get_aces_with_perms(["FILE_DELETE_CHILD", "FILE_ADD_SUBFOLDER"]).get_aces() == []: 189 | self.replaceable_set = 1 190 | self.replaceable = 1 191 | return 1 192 | 193 | # Recursive check of parent directories 194 | # 0: A file/dir can be replaced if it's parent dir can be replaced (doesn't really count as it's a recursive definition) 195 | if self.get_parent_dir() and self.get_parent_dir().get_name() != self.get_name(): # "\" has parent of "\" 196 | if self.get_parent_dir().is_replaceable(): 197 | self.replaceable_set = 1 198 | self.replaceable = 1 199 | return 1 200 | 201 | # File/dir is not replaceable if we get this far 202 | self.replaceable_set = 1 203 | self.replaceable = 0 204 | 205 | # print "[D] is_replaceable returning 0 for %s " % self.get_name() 206 | return 0 207 | 208 | # Doesn't return a trailing slash 209 | def get_parent_dir(self): 210 | #print "get_parent_dir called for: " + self.get_name() 211 | if not self.parent_dir: 212 | mypath = self.get_name() 213 | # Check there is a parent dir - e.g. there isn't for "C:" 214 | if not len(mypath) == 3: # "c:\" 215 | parentpath = "\\".join(mypath.split("\\")[0:-2]) + "\\" 216 | # We frequently refer to parent dirs, so must cache and work we do 217 | self.parent_dir = wpc.conf.cache.File(parentpath) 218 | # print self.parent_dir 219 | else: 220 | # print "[D] no parent dir" 221 | self.parent_dir = None 222 | #if self.parent_dir: 223 | #print "get_parent_dir returning: " + str(self.parent_dir.get_name()) 224 | #else: 225 | #print "get_parent_dir returning: None" 226 | return self.parent_dir 227 | 228 | def get_sd(self): 229 | if self.sd is None: 230 | #sd = None 231 | try: 232 | sd = self.sd = win32security.GetNamedSecurityInfo( 233 | self.get_name(), 234 | win32security.SE_FILE_OBJECT, 235 | win32security.OWNER_SECURITY_INFORMATION | win32security.DACL_SECURITY_INFORMATION 236 | ) 237 | if self.is_dir(): 238 | self.sd = wpc.conf.cache.sd('directory', sd) 239 | else: 240 | self.sd = wpc.conf.cache.sd('file', sd) 241 | except: 242 | print "WARNING: Can't get security descriptor for file: " + self.get_name() 243 | self.sd = None 244 | 245 | return self.sd 246 | 247 | def as_tab(self, dangerous_only=1): 248 | lines = [] 249 | lines.append(wpc.utils.tab_line("info", self.get_type(), str(self.get_name()))) 250 | if self.get_sd(): 251 | lines.append(wpc.utils.tab_line("gotsd", self.get_type(), str(self.get_name()), "yes")) 252 | lines.append(wpc.utils.tab_line("owner", self.get_type(), str(self.get_name()), str(self.get_sd().get_owner().get_fq_name()))) 253 | if self.get_sd().has_dacl(): 254 | lines.append(wpc.utils.tab_line("hasdacl", self.get_type(), str(self.get_name()), "yes")) 255 | if dangerous_only: 256 | lines.extend(self.get_sd().dangerous_aces_as_tab("ace", self.get_type(), str(self.get_name()))) 257 | else: 258 | lines.extend(self.get_sd().aces_as_tab("ace", self.get_type(), str(self.get_name()))) 259 | else: 260 | lines.append(wpc.utils.tab_line("hasdacl", self.get_type(), str(self.get_name()), "no")) 261 | else: 262 | lines.append(wpc.utils.tab_line("gotsd", self.get_type(), str(self.get_name()), "no")) 263 | #print lines 264 | return "\n".join(lines) -------------------------------------------------------------------------------- /wpc/files.py: -------------------------------------------------------------------------------- 1 | from wpc.file import file as File 2 | 3 | 4 | class files: 5 | def __init__(self): 6 | self.files = [] 7 | 8 | # for wpc.file objects, not strings 9 | def add(self, file): 10 | self.files.append(file) 11 | 12 | def add_by_name(self, name): 13 | f = File(name) 14 | self.add(f) 15 | 16 | def get_names(self): 17 | return map(lambda x: x.name, self.getfiles()) 18 | 19 | def get_files(self): 20 | return self.files 21 | 22 | def get_files_by_path(self, ext): 23 | pass # TODO 24 | 25 | def get_files_by_extension(self, exts): 26 | pass # TODO 27 | 28 | def get_files_writable_by_user(self, users): 29 | pass # TODO 30 | 31 | def get_files_writable_by_all_except(self, users): 32 | pass # TODO -------------------------------------------------------------------------------- /wpc/group.py: -------------------------------------------------------------------------------- 1 | from wpc.principal import principal 2 | from wpc.user import user 3 | import ntsecuritycon 4 | import win32security 5 | import wpc.conf 6 | 7 | 8 | # These have members 9 | class group(principal): 10 | def get_members(self): 11 | # print "get_members called for %s" % self.get_fq_name() 12 | return self.get_members_except([self]) 13 | 14 | def get_members_except(self, ignore_principals): 15 | #for i in ignore_principals: 16 | # print "Ignoring: " + i.get_fq_name() 17 | resume = 0 18 | keepgoing = 1 19 | members = [] 20 | principals = [] 21 | #print "group %s is type %s" % (self.get_fq_name(), self.get_type_string()) 22 | #while keepgoing: 23 | #try: 24 | # m, total, resume = win32net.NetLocalGroupGetMembers(wpc.conf.remote_server, self.get_name(), 2 , resume, win32netcon.MAX_PREFERRED_LENGTH) 25 | #except: 26 | # return [] 27 | #print m 28 | #for member in m: 29 | #members.append(member) 30 | # print "[D] a" 31 | for member in wpc.conf.cache.NetLocalGroupGetMembers(wpc.conf.remote_server, self.get_name(), 2): 32 | # print "[D] b" 33 | #print "%s has member %s" % (self.get_fq_name(), member['domainandname']) 34 | p = None 35 | # print "[D] member[sid]: %s" % member['sid'] 36 | if wpc.conf.sid_is_group_type[member['sidusage']]: 37 | # print "[D] b2" 38 | p = group(member['sid']) 39 | # print "[D] b21" 40 | else: 41 | # print "[D] b3" 42 | p = user(member['sid']) 43 | # print "[D] b31" 44 | 45 | #for i in ignore_principals: 46 | # print "checking if %s is %s" % (p.get_sid(), i.get_sid()) 47 | if not p.get_sid() in map(lambda x: x.get_sid(), ignore_principals): 48 | # print "%s is new" % p.get_sid() 49 | principals.append(p) 50 | #else: 51 | # print "%s is NOT new" % p.get_sid() 52 | if not resume: 53 | keepgoing = 0 54 | 55 | # TODO: should be able to list members of group "None" 56 | # print "[D] c" 57 | 58 | # TODO: make this an option 59 | # TODO: If we also want to list members of subgroups recursively... 60 | ignore_principals.extend(principals) 61 | for p in principals: 62 | # print "[D] d" 63 | if p.is_group_type(): 64 | g = group(member['sid']) 65 | # print "[D] %s has member %s (Group)" % (self.get_fq_name(), g.get_fq_name()) 66 | # principals.append(g) 67 | for new_principals in g.get_members_except(ignore_principals): 68 | principals.append(new_principals) 69 | # print "[D] e" 70 | 71 | return principals 72 | -------------------------------------------------------------------------------- /wpc/groups.py: -------------------------------------------------------------------------------- 1 | from wpc.group import group 2 | import win32net 3 | import wpc.conf 4 | import pywintypes 5 | 6 | 7 | class groups(): 8 | def __init__(self): 9 | self.groups = [] 10 | 11 | # TODO need to call with level GROUP_INFO_3. This will get SID and save the slow call to LookupAccountName. 12 | def get_all(self): 13 | if self.groups == []: 14 | try: 15 | level = 0 16 | resume = 0 17 | while True: 18 | grouplist, total, resume = win32net.NetGroupEnum(wpc.conf.remote_server, level, resume, 999999) 19 | for u in grouplist: 20 | try: 21 | sid, name, type = wpc.conf.cache.LookupAccountName(wpc.conf.remote_server, u['name']) 22 | self.groups.append(group(sid)) 23 | except: 24 | print "[E] failed to lookup sid of %s" % group['name'] 25 | if resume == 0: 26 | break 27 | except pywintypes.error as e: 28 | print "[E] %s: %s" % (e[1], e[2]) 29 | try: 30 | level = 0 31 | resume = 0 32 | while True: 33 | grouplist, total, resume = win32net.NetLocalGroupEnum(wpc.conf.remote_server, level, resume, 999999) 34 | for u in grouplist: 35 | try: 36 | sid, name, type = wpc.conf.cache.LookupAccountName(wpc.conf.remote_server, u['name']) 37 | self.groups.append(group(sid)) 38 | except: 39 | print "[E] failed to lookup sid of %s" % group['name'] 40 | if resume == 0: 41 | break 42 | except pywintypes.error as e: 43 | print "[E] %s: %s" % (e[1], e[2]) 44 | return self.groups 45 | -------------------------------------------------------------------------------- /wpc/mspatchdb.py: -------------------------------------------------------------------------------- 1 | import wpc.conf 2 | from zipfile import ZipFile 3 | from lxml import etree as letree 4 | 5 | 6 | class mspatchdb(): 7 | def __init__(self, patchfile): 8 | self.patchfile = patchfile 9 | self.patchspreadsheet = [] 10 | self.parse_spreadsheet(self.patchfile) 11 | 12 | def parse_spreadsheet(self, patchfile): 13 | myzip = ZipFile(patchfile, 'r') 14 | xml = myzip.read('xl/worksheets/sheet1.xml') 15 | xml = xml.replace(" -1 and not row['Affected Product'].find("Media Player") > -1 and (row['Affected Product'].find("Windows 7") > -1 or row['Affected Product'].find("XP") > -1 or row['Affected Product'].find("Server 2008") > -1 or row['Affected Product'].find("Server 2003") > -1 or row['Affected Product'].find("Vista")) > -1: 64 | oslist[row['Affected Product']] = 1 65 | 66 | print "[+] Valid OS strings from xlsx file are:" 67 | for os in sorted(oslist.keys()): 68 | print "%s" % os 69 | 70 | def is_vali_os_string(self, os): 71 | for row in self.patchspreadsheet: 72 | if row['Affected Product'] == os: 73 | return 1 74 | return 0 75 | -------------------------------------------------------------------------------- /wpc/parseOptions.py: -------------------------------------------------------------------------------- 1 | import wpc.utils 2 | from optparse import OptionParser 3 | from optparse import OptionGroup 4 | import sys 5 | 6 | def parseOptions(): 7 | wpc.utils.print_banner() 8 | usage = "%s (--dump [ dump opts] | --dumptab | --audit) [examine opts] [host opts] -o report-file-stem" % (sys.argv[0]) 9 | 10 | parser = OptionParser(usage = usage, version = wpc.utils.get_version()) 11 | examine = OptionGroup(parser, "examine opts", "At least one of these to indicate what to examine (*=not implemented)") 12 | host = OptionGroup(parser, "host opts", "Optional details about a remote host (experimental). Default is current host.") 13 | dump = OptionGroup(parser, "dump opts", "Options to modify the behaviour of dump/dumptab mode") 14 | report = OptionGroup(parser, "report opts", "Reporting options") 15 | 16 | parser.add_option("--dump", dest = "dump_mode", default = False, action = "store_true", help = "Dumps info for you to analyse manually") 17 | parser.add_option("--dumptab", dest = "dumptab_mode",default = False, action = "store_true", help = "Dumps info in tab-delimited format") 18 | parser.add_option("--audit", dest = "audit_mode", default = False, action = "store_true", help = "Identify and report security weaknesses") 19 | parser.add_option("--pyshell", dest = "pyshell_mode", default = False, action = "store_true", help = "Start interactive python shell") 20 | 21 | examine.add_option("-a", "--all", dest = "do_all", default = False, action = "store_true", help = "All Simple Checks (non-slow)") 22 | examine.add_option("-A", "--allfiles", dest = "do_allfiles", default = False, action = "store_true", help = "All Files and Directories (slow)") 23 | examine.add_option("-D", "--drives", dest = "do_drives", default = False, action = "store_true", help = "Drives") 24 | examine.add_option("-e", "--reg_keys", dest = "do_reg_keys", default = False, action = "store_true", help = "Misc security-related reg keys") 25 | examine.add_option("-E", "--eventlogs", dest = "do_eventlogs", default = False, action = "store_true", help = "Event Log*") 26 | examine.add_option("-f", "--interestingfiledir", dest = "interesting_file_list", default = [], action = "append", help = "Changes -A behaviour. Look here INSTEAD") 27 | examine.add_option("-F", "--interestingfilefile",dest = "interesting_file_file", default = False, help = "Changes -A behaviour. Look here INSTEAD. On dir per line") 28 | examine.add_option("-G", "--groups", dest = "do_groups", default = False, action = "store_true", help = "Groups") 29 | examine.add_option("-H", "--shares", dest = "do_shares", default = False, action = "store_true", help = "Shares") 30 | examine.add_option("-I", "--installed_software", dest = "do_installed_software", default = False, action = "store_true", help = "Installed Software") 31 | examine.add_option("-j", "--tasks", dest = "do_scheduled_tasks", default = False, action = "store_true", help = "Scheduled Tasks") 32 | examine.add_option("-k", "--drivers", dest = "do_drivers", default = False, action = "store_true", help = "Kernel Drivers") 33 | examine.add_option("-L", "--loggedin", dest = "do_loggedin", default = False, action = "store_true", help = "Logged In") 34 | examine.add_option("-O", "--ntobjects", dest = "do_nt_objects", default = False, action = "store_true", help = "NT Objects") 35 | examine.add_option("-n", "--nointerestingfiles", dest = "do_interesting_files",default = True, action = "store_false", help = "Changes -A/-f/-F behaviour. Don't report interesting files") 36 | examine.add_option("-N", "--nounreadableif", dest = "do_unreadable_if", default = True, action = "store_false", help = "Changes -A/-f/-F behaviour. Report only interesting files readable by untrsuted users (see -x, -X, -b, -B)") 37 | examine.add_option("-P", "--progfiles", dest = "do_program_files", default = False, action = "store_true", help = "Program Files Directory Tree") 38 | examine.add_option("-r", "--registry", dest = "do_registry", default = False, action = "store_true", help = "Registry Settings + Permissions") 39 | examine.add_option("-R", "--processes", dest = "do_processes", default = False, action = "store_true", help = "Processes") 40 | examine.add_option("-S", "--services", dest = "do_services", default = False, action = "store_true", help = "Windows Services") 41 | examine.add_option("-t", "--paths", dest = "do_paths", default = False, action = "store_true", help = "PATH") 42 | examine.add_option("-T", "--patches", dest = "patchfile", help = "Patches. Arg is filename of xlsx patch info. Download from http://go.microsoft.com/fwlink/?LinkID=245778 or pass 'auto' to fetch automatically") 43 | examine.add_option("-U", "--users", dest = "do_users", default = False, action = "store_true", help = "Users") 44 | examine.add_option("-v", "--verbose", dest = "verbose", default = False, action = "store_true", help = "More verbose output on console") 45 | examine.add_option("-W", "--errors", dest = "do_errors", default = False, action = "store_true", help = "Die on errors instead of continuing (for debugging)") 46 | examine.add_option("-z", "--noappendices",dest = "do_appendices", default = True, action = "store_false",help = "No report appendices in --audit mode") 47 | 48 | host.add_option("-s", "--server", dest = "remote_host", help = "Remote host or IP") 49 | host.add_option("-u", "--user", dest = "remote_user", help = "Remote username") 50 | host.add_option("-p", "--pass", dest = "remote_pass", help = "Remote password") 51 | host.add_option("-d", "--domain", dest = "remote_domain", help = "Remote domain") 52 | 53 | dump.add_option("-M", "--get_modals", dest = "get_modals", default = False, action = "store_true", help = "Dump password policy, etc.") 54 | dump.add_option("-V", "--get_privs", dest = "get_privs", default = False, action = "store_true", help = "Dump privileges for users/groups") 55 | 56 | # Running out of letters for short options. Here's a list of ones used 57 | # abcdefghijklmnopqrstuvwxyz 58 | # uc xxxxxx xxx xxxx xxxxx x 59 | # lc xx xxxxx x xxx xxxxxxx x 60 | 61 | report.add_option("-o", "--report_file_stem", dest = "report_file_stem", default = False, help = "Filename stem for txt, html report files") 62 | report.add_option("-x", "--ignoreprincipal", dest = "ignore_principal_list", default = [], action = "append", help = "Don't report privesc issues for these users/groups") 63 | report.add_option("-X", "--ignoreprincipalfile", dest = "ignore_principal_file", default = False, help = "Don't report privesc issues for these users/groups") 64 | report.add_option("-0", "--ignorenoone", dest = "ignorenoone", default = False, action = "store_true",help = "No one is trusted (even Admin, SYSTEM). hyphen zero") 65 | report.add_option("-c", "--exploitablebycurrentuser", dest = "exploitable_by_me", default = False, action = "store_true",help = "Report only privesc issues relating to current user") 66 | report.add_option("-b", "--exploitableby", dest = "exploitable_by_list", default = [], action = "append", help = "Report privesc issues only for these users/groups") 67 | report.add_option("-B", "--exploitablebyfile", dest = "exploitable_by_file", default = False, help = "Report privesc issues only for these user/groupss") 68 | 69 | parser.add_option_group(examine) 70 | parser.add_option_group(host) 71 | parser.add_option_group(dump) 72 | parser.add_option_group(report) 73 | 74 | (options, _) = parser.parse_args() 75 | 76 | if not options.dump_mode and not options.audit_mode and not options.dumptab_mode and not options.pyshell_mode: 77 | print "[E] Specify mode using --dump, --audit, --dumptab or --pyshell. -h for help." 78 | sys.exit() 79 | 80 | if options.dump_mode or options.audit_mode or options.dumptab_mode: 81 | if not (options.do_all or options.do_services or options.do_drivers or options.do_processes or options.patchfile or options.do_reg_keys or options.do_registry or options.do_users or options.do_groups or options.do_program_files or options.do_paths or options.do_drives or options.do_eventlogs or options.do_shares or options.do_loggedin or options.do_users or options.do_groups or options.do_allfiles or options.get_modals or options.do_scheduled_tasks or options.do_nt_objects or options.do_installed_software or options.interesting_file_list or options.interesting_file_file): 82 | print "[E] Specify something to look at. At least one of: -a, -j, -O, -t, -D, -E, -e, -H, -T, -L , -S, -k, -I, -U, -s, -d, -P, -r, -R, -U, -G, -M. -h for help." 83 | sys.exit() 84 | 85 | if options.ignorenoone and not (options.ignore_principal_list or options.ignore_principal_file): 86 | print "[W] -0 (--ignorenoone) specified without -x or -X. This is a crazy thing to do in --audit mode. Output of --dump/--dumptab will be huge!" 87 | 88 | if options.audit_mode and not options.report_file_stem: 89 | print "[E] Specify report filename stem, e.g. '-o report-myhost'. -h for help." 90 | sys.exit() 91 | 92 | if options.exploitable_by_me and (options.ignore_principal_list or options.ignore_principal_file or options.exploitable_by_list or options.exploitable_by_file): 93 | print "[E] When using -c, it doesn't make sense to use -x, -X, -b or -B" 94 | sys.exit() 95 | 96 | if (options.ignore_principal_list or options.ignore_principal_file) and (options.exploitable_by_list or options.exploitable_by_file): 97 | print "[E] When using -b or -B, it doesn't make sense to use -x or -X" 98 | sys.exit() 99 | 100 | # TODO check file is writable. 101 | # TODO can't use -m without -G 102 | 103 | return options 104 | -------------------------------------------------------------------------------- /wpc/patchdata.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import os 3 | import re 4 | from wpc.mspatchdb import mspatchdb 5 | 6 | 7 | # These have members 8 | class patchdata(): 9 | def __init__(self, opts): 10 | self.installed_patches = [] 11 | self.os = {} 12 | self.verbose = 0 13 | if 'verbose' in opts: 14 | self.verbose = opts['verbose'] 15 | self.os['spreadsheet_string'] = None 16 | if 'os_string' in opts: 17 | self.os['spreadsheet_string'] = opts['os_string'] 18 | self.os["info"] = {} 19 | self.db = None 20 | if 'patchdb' in opts.keys(): 21 | self.db = opts['patchdb'] 22 | elif 'patchfile' in opts.keys(): 23 | self.db = mspatchdb({'file': opts['patchfile']}) 24 | else: 25 | self.db = mspatchdb() 26 | 27 | def record_installed_patch(self, patch): 28 | # TODO dedup 29 | self.installed_patches.append(patch) 30 | 31 | def get_installed_patches(self): 32 | if not self.installed_patches: 33 | self.parse_installed_patches_from_systeminfo() 34 | return self.installed_patches 35 | 36 | def get_os_info(self): 37 | return self.os['info'] 38 | 39 | def parse_installed_patches_from_systeminfo(self): 40 | output = subprocess.check_output("systeminfo", stderr = open(os.devnull, 'w')) 41 | for line in output.splitlines(): 42 | m = re.search("OS Name:\s+.*Windows(?:\(R\))? (7|XP|Server 2003|Vista|Server 2008 R2|Server 2012)", line) 43 | if m and m.group(1): 44 | self.os['info']['winver'] = m.group(1) 45 | 46 | m = re.search("OS Version:.*Service Pack (\d+)", line) 47 | if m and m.group(1): 48 | self.os['info']['sp'] = m.group(1) 49 | 50 | m = re.search("System [Tt]ype:.*(86|64)", line) 51 | if m and m.group(1): 52 | self.os['info']['arch'] = m.group(1) 53 | 54 | m = re.search("^\s+\[\d+\]:\s+(?:KB|Q)?(\d{6,7})", line) 55 | if m and m.group(1): 56 | # print "[+] Found installed patch: %s" % m.group(1) 57 | self.record_installed_patch(m.group(1)) 58 | 59 | def get_os_string_for_ms_spreadsheet(self): 60 | if not ('spreadsheet_string' in self.os and self.os['spreadsheet_string']): 61 | self.os['spreadsheet_string'] = self.guess_os_string_for_ms_spreadsheet() 62 | return self.os['spreadsheet_string'] 63 | 64 | def guess_os_string_for_ms_spreadsheet(self): 65 | if self.os['info']['winver']: 66 | if self.os['info']['winver'].find("XP") > 0 or self.os['info']['winver'].find("2003") > 0 or self.os['info']['winver'].find("NT") > 0 or self.os['info']['winver'].find("2000") > 0: 67 | os = "Microsoft Windows %s" % self.os['info']['winver'] 68 | else: 69 | os = "Windows %s" % self.os['info']['winver'] 70 | 71 | if 'arch' in self.os['info'].keys() and self.os['info']['arch']: 72 | if self.os['info']['winver'].find("Vista") > -1: 73 | if self.os['info']['arch'] == "64": 74 | os = "%s x64 Edition" % os 75 | else: 76 | if self.os['info']['arch'] == "64": 77 | os = "%s for x64-based Systems" % os 78 | if self.os['info']['arch'] == "32": 79 | os = "%s for 32-bit Systems" % os 80 | 81 | if 'sp' in self.os['info'].keys() and self.os['info']['sp']: 82 | os = "%s Service Pack %s" % (os, self.os['info']['sp']) 83 | 84 | return os 85 | 86 | def is_msno_applied(self, msno): 87 | kbs = self.db.get_kbs_from_msno(msno, self.get_os_string_for_ms_spreadsheet()) 88 | for kb in kbs: 89 | if kb in self.get_installed_patches(): 90 | return 1 91 | return 0 92 | 93 | def msno_or_superseded_applied(self, msno, os, depth): 94 | m = re.search("(MS\d\d-\d\d\d)", msno) 95 | if m and m.group(1): 96 | msno = m.group(1) 97 | else: 98 | print "[E] Illegal msno passed: %s" % msno 99 | if self.is_msno_applied(msno): 100 | if depth == 0: 101 | if self.verbose: 102 | print "[+] %s has been patched" % msno 103 | return 1 104 | else: 105 | s = self.db.superseding_patch(msno, os) 106 | if s: 107 | at_least_one_superseding_patch_applied = 0 108 | for patch_string in s.split(","): 109 | m = re.search("(MS\d\d-\d\d\d)", patch_string) 110 | if m and m.group(1): 111 | if m.group(1) == msno: 112 | if self.verbose: 113 | print "[+] %s supersedes %s (ignoring)" % (m.group(1), msno) 114 | continue 115 | 116 | if self.msno_or_superseded_applied(patch_string, os, depth + 1): 117 | at_least_one_superseding_patch_applied = 1 118 | if self.verbose: 119 | print "[+] %s supersedes %s (and has been applied)" % (m.group(1), msno) 120 | return 1 121 | else: 122 | if self.verbose: 123 | print "[+] %s supersedes %s (and has NOT been patched)" % (m.group(1), msno) 124 | 125 | if not at_least_one_superseding_patch_applied: 126 | if depth == 0 and self.verbose: 127 | print "[+] VULNERABLE. %s has not been patched. There are superseding patches but none have been applied." % (msno) 128 | return 0 129 | else: 130 | if depth == 0 and self.verbose: 131 | print "[+] VULNERABLE. %s has not been patched and it has no superseding patches." % (msno) 132 | return 0 133 | -------------------------------------------------------------------------------- /wpc/principal.py: -------------------------------------------------------------------------------- 1 | import win32security 2 | import ntsecuritycon 3 | import _winreg 4 | import win32service 5 | import win32con 6 | import wpc.conf 7 | 8 | # These have sids, perhaps a domain 9 | class principal: 10 | def __init__(self, sid): 11 | self.name = None 12 | # self.info = None 13 | self.domain = None 14 | self.set_sid(sid) 15 | self.type = None 16 | self.sid_string = None 17 | self.trusted = None 18 | self.trusted_set = 0 19 | self.cant_resolve = 0 20 | self.privileges = None 21 | self.info = {} 22 | # self.info['sid'] = self.get_sid_string() 23 | # self.info['privileges'] = " ".join(self.get_privileges()) 24 | 25 | def set_info(self, info): 26 | self.info = info 27 | 28 | def add_info(self, h): 29 | for k in h.keys(): 30 | self.info[k] = h[k] 31 | 32 | def get_info(self): 33 | return self.info 34 | 35 | def set_sid(self, sid): 36 | self.sid = sid 37 | 38 | def get_remote_server(self): 39 | return wpc.conf.remote_server 40 | 41 | def get_sid(self): 42 | return self.sid 43 | 44 | def get_sid_string(self): 45 | if self.sid_string == None: 46 | self.sid_string = win32security.ConvertSidToStringSid(self.get_sid()) 47 | return self.sid_string 48 | 49 | def get_fq_name(self): 50 | if self.cant_resolve: 51 | return self.get_sid_string() 52 | else: 53 | return self.get_domain() + "\\" + self.get_name() 54 | 55 | def get_type(self): 56 | if self.type == None: 57 | self.get_name() # side effect sets type 58 | return self.type 59 | 60 | # def get_principal(self): 61 | #if self.__class__.__name__ == "principal": 62 | #return self 63 | #else: 64 | #return self.principal 65 | 66 | def get_domain(self): 67 | if self.domain == None: 68 | self.get_name() # side effect sets domain 69 | return self.domain 70 | 71 | def set_type(self, principal_type): 72 | self.type = principal_type 73 | 74 | def get_type_string(self): 75 | return self.resolve_type(self.get_type()) 76 | 77 | def resolve_type(self, type): 78 | return wpc.conf.sid_type[type] 79 | 80 | def is_group_type(self): 81 | return wpc.conf.sid_is_group_type[self.get_type()] 82 | 83 | def set_domain(self, domain): 84 | self.domain = domain 85 | 86 | def get_privileges(self): 87 | if not self.privileges is None: 88 | return self.privileges 89 | 90 | self.privileges = [] 91 | try: 92 | ph = wpc.conf.cache.LsaOpenPolicy(wpc.conf.remote_server, win32security.POLICY_VIEW_LOCAL_INFORMATION | win32security.POLICY_LOOKUP_NAMES) 93 | self.privileges = wpc.conf.cache.LsaEnumerateAccountRights(ph, self.get_sid()) 94 | except: 95 | pass 96 | 97 | return self.privileges 98 | 99 | def get_name(self): 100 | if self.name == None or self.get_type() == None: 101 | sid = self.get_sid() 102 | if sid == None: 103 | self.set_type('N/A') 104 | self.set_domain('[none]') 105 | self.name = '[none]' 106 | else: 107 | try: 108 | #print wpc.conf.cache.LookupAccountSid(self.get_remote_server(), self.get_sid()) 109 | self.name, domain, sid_type = list(wpc.conf.cache.LookupAccountSid(self.get_remote_server(), self.get_sid())) 110 | except: 111 | self.cant_resolve = 1 112 | self.name, domain, sid_type = self.get_sid_string(), "[unknown]", 8 113 | self.set_type(sid_type) 114 | self.set_domain(domain) 115 | return self.name 116 | 117 | # def is_trusted(self): 118 | # for p in wpc.conf.trusted_principals: 119 | # if self.get_sid() == p.get_sid(): 120 | # return 1 121 | # return 0 122 | 123 | def is_trusted(self): 124 | if wpc.conf.privesc_mode == "report_untrusted": 125 | # print "Testing if %s is trusted" % self.get_fq_name() 126 | if self.trusted_set: 127 | #print "Cache result returned for trust of %s: %s" % (self.get_fq_name(), self.trusted) 128 | return self.trusted 129 | 130 | # TODO optimize this. It's called a LOT! 131 | if self.is_group_type() and self.get_type() == 4: 132 | g = wpc.group.group(self.get_sid()) 133 | # Groups with zero members are trusted - i.e. not interesting 134 | if len(g.get_members()) == 0: 135 | self.trusted_set = 1 136 | self.trusted = 1 137 | #print "Ignoring empty group %s (type %s)" % (self.get_fq_name(), self.get_type()) 138 | return 1 139 | 140 | for p in wpc.conf.trusted_principals: 141 | # This also recurses through sub groups 142 | # print "Testing if %s is in %s" % (self.get_fq_name(), p.get_fq_name()) 143 | # print "[D] pincipal.is_trusted: %s is group? %s" % (p.get_fq_name(), p.is_group_type()) 144 | # print "[D] self.is_in_group(p): %s" % (self.is_in_group(p)) 145 | if p.is_group_type() and self.is_in_group(p): 146 | # print "Yes" 147 | self.trusted_set = 1 148 | self.trusted = 1 149 | # print "%s is trusted. Member of trusted group %s" % (self.get_fq_name(), p.get_fq_name()) 150 | return 1 151 | else: 152 | #print "No" 153 | # print "User type" 154 | if p.get_sid() == self.get_sid(): 155 | self.trusted_set = 1 156 | self.trusted = 1 157 | #print "%s is trusted. Is trusted user %s" % (self.get_fq_name(), p.get_fq_name()) 158 | return 1 159 | self.trusted_set = 1 160 | self.trusted = 0 161 | #print "%s is not trusted" % self.get_fq_name() 162 | return 0 163 | 164 | # Principals on the exploitable_by list are NOT trusted. Everyone else is. 165 | if wpc.conf.privesc_mode == "exploitable_by": 166 | for p in wpc.conf.exploitable_by: 167 | if p.get_sid() == self.get_sid(): 168 | self.trusted_set = 1 169 | self.trusted = 0 170 | return 0 171 | self.trusted_set = 1 172 | self.trusted = 1 173 | return 1 174 | 175 | 176 | def is_in_group(self, group): 177 | # print "is_in_group called for %s, %s" % (self.get_fq_name(), group.get_name()) 178 | return wpc.conf.cache.is_in_group(self, group) 179 | 180 | -------------------------------------------------------------------------------- /wpc/process.py: -------------------------------------------------------------------------------- 1 | from wpc.file import file as File 2 | from wpc.sd import sd 3 | from wpc.token import token 4 | from wpc.thread import thread 5 | import win32api 6 | import win32con 7 | import win32process 8 | import win32security 9 | import wpc.utils 10 | import ctypes 11 | import win32job 12 | 13 | class THREADENTRY32(ctypes.Structure): 14 | _fields_ = [ 15 | ("dwSize", ctypes.c_ulong), 16 | ("cntUsage", ctypes.c_ulong), 17 | ("th32ThreadID", ctypes.c_ulong), 18 | ("th32OwnerProcessID", ctypes.c_ulong), 19 | ("tpBasePri", ctypes.c_ulong), 20 | ("tpDeltaPri", ctypes.c_ulong), 21 | ("dwFlags", ctypes.c_ulong) 22 | ] 23 | 24 | class process: 25 | def __init__(self, pid): 26 | self.pid = pid 27 | self.ph = None 28 | self.pth = None 29 | self.exe = None 30 | self.exe_path_dirty = None 31 | self.exe_path_clean = None 32 | self.wow64 = None 33 | self.mhs = None 34 | self.dlls = [] 35 | self.wts_name = None 36 | self.wts_session_id = None 37 | self.wts_sid = None 38 | self.token = None 39 | self.thread_ids = [] 40 | self.threads = [] 41 | self.short_name = "[none]" 42 | self.sd = None 43 | 44 | def get_pid(self): 45 | return self.pid 46 | 47 | def add_thread_id(self, tid): 48 | if not int(tid) in self.thread_ids: 49 | self.thread_ids.append(int(tid)) 50 | 51 | def add_thread(self, t): 52 | t.set_parent_process(self) 53 | self.threads.append(t) 54 | 55 | def get_type(self): 56 | return 'process' 57 | 58 | def get_thread_ids(self): 59 | if not self.thread_ids: 60 | TH32CS_SNAPTHREAD = 0x00000004 61 | 62 | CreateToolhelp32Snapshot = ctypes.windll.kernel32.CreateToolhelp32Snapshot 63 | Thread32First = ctypes.windll.kernel32.Thread32First 64 | Thread32Next = ctypes.windll.kernel32.Thread32Next 65 | CloseHandle = ctypes.windll.kernel32.CloseHandle 66 | 67 | hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, self.get_pid()) 68 | te32 = THREADENTRY32() 69 | te32.dwSize = ctypes.sizeof(THREADENTRY32) 70 | if Thread32First(hThreadSnap, ctypes.byref(te32)) == win32con.FALSE: 71 | pass 72 | #print >> sys.stderr, "Failed getting first process." 73 | #return 74 | else: 75 | while True: 76 | # TODO can we just get thread info for a single process instead? 77 | # print "PID: %s, TID: %s" % (te32.th32OwnerProcessID, te32.th32ThreadID) 78 | if self.get_pid() == te32.th32OwnerProcessID: 79 | self.add_thread_id(te32.th32ThreadID) 80 | 81 | if Thread32Next(hThreadSnap, ctypes.byref(te32)) == win32con.FALSE: 82 | break 83 | CloseHandle(hThreadSnap) 84 | return sorted(self.thread_ids) 85 | 86 | def get_thread_count(self): 87 | return len(self.thread_ids) 88 | 89 | def get_tokens(self): 90 | self.token_handles = [] 91 | 92 | # Process Token 93 | tok = self.get_token() 94 | if tok: 95 | self.token_handles.append(tok) 96 | 97 | # Thread Tokens 98 | for t in self.get_threads(): 99 | tok = t.get_token() 100 | if tok: 101 | self.token_handles.append(tok) 102 | 103 | return self.token_handles 104 | 105 | def get_token_handles_int(self): 106 | self.token_handles_int = [] 107 | for t in self.get_tokens(): 108 | self.token_handles_int.append(t.get_th_int()) 109 | 110 | return self.token_handles_int 111 | 112 | def get_threads(self): 113 | if not self.threads: 114 | for t in self.get_thread_ids(): 115 | self.add_thread(thread(t)) 116 | return self.threads 117 | 118 | def set_wts_name(self, wts_name): 119 | self.wts_name = wts_name 120 | 121 | def set_short_name(self, n): 122 | self.short_name = n 123 | 124 | def get_short_name(self): 125 | return self.short_name 126 | 127 | def get_wts_session_id(self): 128 | return self.wts_session_id 129 | 130 | def set_wts_session_id(self, wts_session_id): 131 | self.wts_session_id = wts_session_id 132 | 133 | def get_wts_sid(self): 134 | return self.wts_sid 135 | 136 | def set_wts_sid(self, wts_sid): 137 | self.wts_sid = wts_sid 138 | 139 | def get_wts_name(self): 140 | return self.wts_name 141 | 142 | def get_sd(self): 143 | if not self.sd: 144 | try: 145 | secdesc = win32security.GetSecurityInfo(self.get_ph(), win32security.SE_KERNEL_OBJECT, win32security.DACL_SECURITY_INFORMATION | win32security.OWNER_SECURITY_INFORMATION | win32security.GROUP_SECURITY_INFORMATION) 146 | self.sd = sd('process', secdesc) 147 | except: 148 | pass 149 | return self.sd 150 | 151 | def get_mhs(self): 152 | if not self.mhs: 153 | if self.get_ph(): 154 | try: 155 | mhs = win32process.EnumProcessModules(self.get_ph()) 156 | self.mhs = list(mhs) 157 | except: 158 | pass 159 | return self.mhs 160 | 161 | def get_dlls(self): 162 | if self.dlls == []: 163 | if self.get_mhs(): 164 | for mh in self.get_mhs(): 165 | dll = win32process.GetModuleFileNameEx(self.get_ph(), mh) 166 | #print dll 167 | self.dlls.append(File(dll)) 168 | #dump_perms(dll, 'file', {'brief': 1}) 169 | return self.dlls 170 | 171 | def get_exe_path_clean(self): 172 | if not self.exe_path_clean: 173 | self.exe_path_clean = wpc.utils.get_exe_path_clean(self.get_exe_path_dirty()) 174 | if not self.exe_path_clean: 175 | self.exe_path_clean = self.get_exe_path_dirty() 176 | return self.exe_path_clean 177 | 178 | def get_exe_path_dirty(self): 179 | if not self.exe_path_dirty: 180 | if self.get_mhs(): 181 | self.exe_path_dirty = win32process.GetModuleFileNameEx(self.get_ph(), self.get_mhs().pop(0)) 182 | return self.exe_path_dirty 183 | 184 | def get_exe(self): 185 | if not self.exe: 186 | if self.get_exe_path_dirty(): 187 | self.exe = File(self.get_exe_path_clean()) 188 | return self.exe 189 | 190 | def is_in_job(self): 191 | try: 192 | if win32job.IsProcessInJob(self.get_ph(), 0): 193 | return 1 194 | except: 195 | pass 196 | return 0 197 | 198 | def get_ph(self): 199 | if not self.ph: 200 | try: 201 | # PROCESS_ALL_ACCESS needed to get security descriptor 202 | self.ph = win32api.OpenProcess(win32con.PROCESS_ALL_ACCESS, False, self.get_pid()) 203 | #print "OpenProcess with PROCESS_ALL_ACCESS: Success" 204 | except: 205 | try: 206 | # PROCESS_VM_READ is required to list modules (DLLs, EXE) 207 | self.ph = win32api.OpenProcess(win32con.PROCESS_VM_READ | win32con.PROCESS_QUERY_INFORMATION, False, self.get_pid()) 208 | #print "OpenProcess with VM_READ and PROCESS_QUERY_INFORMATION: Success" 209 | except: 210 | #print "OpenProcess with VM_READ and PROCESS_QUERY_INFORMATION: Failed" 211 | try: 212 | # We can still get some info without PROCESS_VM_READ 213 | self.ph = win32api.OpenProcess(win32con.PROCESS_QUERY_INFORMATION, False, self.get_pid()) 214 | #print "OpenProcess with PROCESS_QUERY_INFORMATION: Success" 215 | except: 216 | #print "OpenProcess with PROCESS_QUERY_INFORMATION: Failed" 217 | try: 218 | # If we have to resort to using PROCESS_QUERY_LIMITED_INFORMATION, the process is protected. 219 | # There's no point trying PROCESS_VM_READ 220 | # Ignore pydev warning. We define this at runtime because win32con is out of date. 221 | self.ph = win32api.OpenProcess(win32con.PROCESS_QUERY_LIMITED_INFORMATION, False, self.get_pid()) 222 | #print "OpenProcess with PROCESS_QUERY_LIMITED_INFORMATION: Success" 223 | except: 224 | #print "OpenProcess with PROCESS_QUERY_LIMITED_INFORMATION: Failed" 225 | self.ph = None 226 | return self.ph 227 | 228 | def get_pth(self): 229 | if not self.pth: 230 | try: 231 | self.pth = win32security.OpenProcessToken(self.get_ph(), win32con.TOKEN_ALL_ACCESS) 232 | except: 233 | try: 234 | self.pth = win32security.OpenProcessToken(self.get_ph(), win32con.TOKEN_READ) 235 | except: 236 | try: 237 | self.pth = win32security.OpenProcessToken(self.get_ph(), win32con.TOKEN_QUERY) 238 | #print "OpenProcessToken with TOKEN_QUERY: Failed" 239 | except: 240 | pass 241 | return self.pth 242 | 243 | def is_wow64(self): 244 | if not self.wow64 and self.get_ph(): 245 | self.wow64 = win32process.IsWow64Process(self.get_ph()) 246 | return self.wow64 247 | 248 | def get_token(self): 249 | if not self.token: 250 | if self.get_pth(): 251 | self.token = token(self.get_pth()) 252 | return self.token 253 | 254 | def as_text(self): 255 | t = '' 256 | t += "-------------------------------------------------\n" 257 | t += "PID: " + str(self.get_pid()) + "\n" 258 | t += "Short Name: " + str(self.get_short_name()) + "\n" 259 | t += "WTS Name: " + str(self.get_wts_name()) + "\n" 260 | t += "WTS Session ID: " + str(self.get_wts_session_id()) + "\n" 261 | if self.get_wts_sid(): 262 | t += "WTS Sid: " + str(self.get_wts_sid().get_fq_name()) + "\n" 263 | else: 264 | t += "WTS Sid: None\n" 265 | t += "Access Token Count: %s\n" % len(self.get_token_handles_int()) 266 | t += "Access Token Handles: %s\n" % ",".join(str(x) for x in self.get_token_handles_int()) 267 | t += "In Job?: %s\n" % self.is_in_job() 268 | t += "Thread Count: %s\n" % self.get_thread_count() 269 | t += "Thread IDs: %s\n" % ",".join(str(x) for x in self.get_thread_ids()) 270 | if self.get_ph(): 271 | t += "Is WOW64: " + str(self.is_wow64()) + "\n" 272 | if self.get_exe(): 273 | t += "Exe: " + str(self.get_exe().get_name()) + "\n" 274 | else: 275 | t += "Exe: [unknown]\n" 276 | t += "Modules:\n" 277 | for dll in self.get_dlls(): 278 | t += "\t\t" + dll.get_name() + "\n" 279 | 280 | t += "\nProcess Security Descriptor:\n" 281 | if self.get_sd(): 282 | t += self.get_sd().as_text() 283 | 284 | t += "\nProcess Access Token:\n" 285 | if self.get_token(): 286 | t += self.get_token().as_text() 287 | else: 288 | t += "[unknown]\n" 289 | 290 | for td in self.get_threads(): 291 | #thread_obj = thread(tid) 292 | #print "Dumping thread object" 293 | ttext = td.as_text() 294 | #print "ttext: %s" % ttext 295 | t += ttext 296 | t += "\n" 297 | return t 298 | 299 | def get_pid_and_name(self): 300 | return "%s[%s]" % (self.get_pid(), self.get_short_name()) 301 | 302 | def as_tab(self, dangerous_only=1): 303 | lines = [] 304 | exe = "" 305 | if self.get_ph(): 306 | if self.get_exe(): 307 | exe = self.get_exe().get_name() 308 | 309 | wts_name = "" 310 | if self.get_wts_sid(): 311 | wts_name = self.get_wts_sid().get_fq_name() 312 | 313 | token_id = "" 314 | if self.get_token(): 315 | token_id = self.get_token().get_th_int() 316 | lines.append(self.get_token().as_tab()) 317 | 318 | lines.append(wpc.utils.tab_line("info", self.get_type(), self.get_pid(), self.get_short_name(), exe, token_id, self.is_wow64(), wts_name, self.get_wts_session_id(), self.is_in_job(), ",".join(str(x) for x in self.get_thread_ids()))) 319 | 320 | if self.get_ph(): 321 | for dll in self.get_dlls(): 322 | lines.append(wpc.utils.tab_line("info", "process_module", self.get_pid(), dll.get_name() )) 323 | 324 | if self.get_sd(): 325 | lines.append(wpc.utils.tab_line("gotsd", self.get_type(), str(self.get_pid_and_name()), "yes")) 326 | lines.append(wpc.utils.tab_line("owner", self.get_type(), str(self.get_pid_and_name()), str(self.get_sd().get_owner().get_fq_name()))) 327 | if self.get_sd().has_dacl(): 328 | lines.append(wpc.utils.tab_line("hasdacl", self.get_type(), str(self.get_pid_and_name()), "yes")) 329 | if dangerous_only: 330 | lines.extend(self.get_sd().dangerous_aces_as_tab("ace", self.get_type(), str(self.get_pid_and_name()))) 331 | else: 332 | lines.extend(self.get_sd().aces_as_tab("ace", self.get_type(), str(self.get_pid_and_name()))) 333 | else: 334 | lines.append(wpc.utils.tab_line("hasdacl", self.get_type(), str(self.get_pid_and_name()), "no")) 335 | else: 336 | lines.append(wpc.utils.tab_line("gotsd", self.get_type(), str(self.get_pid_and_name()), "no")) 337 | for td in self.get_threads(): 338 | #thread_obj = thread(tid) 339 | #print "Dumping thread object" 340 | lines.append(td.as_tab()) 341 | #print lines 342 | return "\n".join(lines) -------------------------------------------------------------------------------- /wpc/processes.py: -------------------------------------------------------------------------------- 1 | from wpc.principal import principal 2 | from wpc.process import process 3 | import win32process 4 | import win32ts 5 | import wpc.conf 6 | import ctypes 7 | import win32con 8 | 9 | class PROCESSENTRY32(ctypes.Structure): 10 | _fields_ = [("dwSize", ctypes.c_ulong), 11 | ("cntUsage", ctypes.c_ulong), 12 | ("th32ProcessID", ctypes.c_ulong), 13 | ("th32DefaultHeapID", ctypes.c_ulong), 14 | ("th32ModuleID", ctypes.c_ulong), 15 | ("cntThreads", ctypes.c_ulong), 16 | ("th32ParentProcessID", ctypes.c_ulong), 17 | ("pcPriClassBase", ctypes.c_ulong), 18 | ("dwFlags", ctypes.c_ulong), 19 | ("szExeFile", ctypes.c_char * 260)] 20 | 21 | class processes: 22 | def __init__(self): 23 | self.processes = [] 24 | 25 | def add(self, p): 26 | self.processes.append(p) 27 | 28 | def get_all(self): 29 | if self.processes == []: 30 | pids = win32process.EnumProcesses() 31 | try: 32 | proc_infos = win32ts.WTSEnumerateProcesses(wpc.conf.remote_server, 1, 0) 33 | except: 34 | proc_infos = [] 35 | pass 36 | 37 | for pid in pids: 38 | p = process(pid) 39 | self.add(p) 40 | 41 | for proc_info in proc_infos: 42 | pid = proc_info[1] 43 | p = self.find_by_pid(pid) 44 | if p: # might fail to find process - race condition 45 | p.set_wts_session_id(proc_info[0]) 46 | p.set_wts_name(proc_info[2]) 47 | if proc_info[3]: # sometimes None 48 | p.set_wts_sid(principal(proc_info[3])) 49 | 50 | TH32CS_SNAPPROCESS = 0x00000002 51 | 52 | # See http://msdn2.microsoft.com/en-us/library/ms686701.aspx 53 | CreateToolhelp32Snapshot = ctypes.windll.kernel32.CreateToolhelp32Snapshot 54 | Process32First = ctypes.windll.kernel32.Process32First 55 | Process32Next = ctypes.windll.kernel32.Process32Next 56 | Thread32First = ctypes.windll.kernel32.Thread32First 57 | Thread32Next = ctypes.windll.kernel32.Thread32Next 58 | CloseHandle = ctypes.windll.kernel32.CloseHandle 59 | 60 | hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) 61 | pe32 = PROCESSENTRY32() 62 | pe32.dwSize = ctypes.sizeof(PROCESSENTRY32) 63 | if Process32First(hProcessSnap, ctypes.byref(pe32)) == win32con.FALSE: 64 | pass 65 | #print >> sys.stderr, "Failed getting first process." 66 | #return 67 | else: 68 | while True: 69 | p = self.find_by_pid(pe32.th32ProcessID) 70 | if p: # might fail to find process - race condition 71 | p.set_short_name(pe32.szExeFile) 72 | 73 | if Process32Next(hProcessSnap, ctypes.byref(pe32)) == win32con.FALSE: 74 | break 75 | CloseHandle(hProcessSnap) 76 | 77 | return self.processes 78 | 79 | def find_by_pid(self, pid): 80 | for p in self.processes: 81 | if p.pid == pid: 82 | return p 83 | return None -------------------------------------------------------------------------------- /wpc/regkey.py: -------------------------------------------------------------------------------- 1 | from wpc.report.issueAcl import issueAcl 2 | from wpc.sd import sd 3 | import ntsecuritycon 4 | import win32api 5 | import win32con 6 | import win32security 7 | import wpc.conf 8 | 9 | 10 | # regkeys or directories 11 | class regkey: 12 | def __init__(self, key_string, **kwargs): 13 | # print "[D] Created regkey obj for " + name 14 | self.sd = None 15 | self.keyh = None 16 | self.hive = None 17 | self.path = None 18 | self.set_name(key_string) 19 | self.parent_key = None 20 | self.view = None 21 | if 'view' in kwargs.keys(): 22 | self.view = kwargs['view'] # 64 or 32 23 | 24 | def set_name(self, key_string): 25 | parts = key_string.split("\\") 26 | if parts[0] == "HKLM": 27 | parts[0] = "HKEY_LOCAL_MACHINE" 28 | self.set_hive(parts[0]) 29 | self.set_path("\\".join(parts[1:])) 30 | 31 | def get_view(self): 32 | return self.view 33 | 34 | def get_hive(self): 35 | return self.hive 36 | 37 | def set_path(self, path): 38 | if path == '\\': 39 | path = '' 40 | self.path = path 41 | 42 | def get_path(self): 43 | return self.path 44 | 45 | def set_hive(self, hive): 46 | self.hive = hive 47 | 48 | def as_text(self): 49 | s = "Registry key: " + self.get_name() + "\n" 50 | if self.get_sd(): 51 | s += self.get_sd().as_text() 52 | else: 53 | s += "[ERROR]" 54 | return s 55 | 56 | def get_parent_key(self): 57 | #print "get_parent_key called for: " + self.get_name() 58 | if not self.parent_key: 59 | mypath = self.get_name() 60 | # Check there is a parent_key dir - e.g. there isn't for "HKEY_LOCAL_MACHINE" 61 | if not mypath.find("\\") == -1: 62 | # check if only slash is at end of string: "HKEY_LOCAL_MACHINE\" 63 | if mypath.find("\\") == len(mypath) - 1: 64 | self.parent_key = None 65 | else: 66 | parent_keypath = "\\".join(mypath.split("\\")[0:-1]) 67 | # We frequently refer to parent_key dirs, so must cache and work we do 68 | self.parent_key = wpc.conf.cache.regkey(parent_keypath) 69 | # print self.parent_key 70 | else: 71 | # print "[D] no parent_key dir" 72 | self.parent_key = None 73 | #if self.parent_key: 74 | #print "get_parent_key returning: " + str(self.parent_key.get_name()) 75 | #else: 76 | #print "get_parent_key returning: None" 77 | return self.parent_key 78 | 79 | def get_issue_acl_for_perms(self, perms): 80 | if self.get_sd(): 81 | al = self.get_sd().get_acelist().get_untrusted().get_aces_with_perms(perms).get_aces() 82 | if al == []: 83 | return None 84 | else: 85 | return issueAcl(self.get_name(), al) 86 | 87 | def dump(self): 88 | print self.as_text() 89 | 90 | def get_all_subkeys(self): 91 | for key in self.get_subkeys(): 92 | yield key 93 | for k in key.get_all_subkeys(): 94 | yield k 95 | 96 | def get_subkeys(self): 97 | subkey_objects = [] 98 | try: 99 | subkeys = win32api.RegEnumKeyEx(self.get_keyh()) 100 | for subkey in subkeys: 101 | subkey_objects.append(regkey(self.get_name() + "\\" + subkey[0], view=self.view)) 102 | except: 103 | pass 104 | return subkey_objects 105 | 106 | def get_value(self, v): 107 | try: 108 | (data, type) = win32api.RegQueryValueEx(self.get_keyh(), v) 109 | return data 110 | except: 111 | return None 112 | 113 | def get_values(self): 114 | try: 115 | values = [] 116 | (subkey_count, value_count, mod_time) = win32api.RegQueryInfoKey(self.get_keyh()) 117 | for i in range(0, value_count): 118 | (s, o, t) = win32api.RegEnumValue(self.get_keyh(), i) 119 | values.append(s) 120 | return values 121 | except: 122 | return [] 123 | 124 | def get_name(self): 125 | if self.path == '': 126 | return self.hive 127 | return self.hive + "\\" + self.path 128 | 129 | def get_keyh(self): 130 | if not self.keyh: 131 | try: 132 | # self.keyh = win32api.RegOpenKeyEx(getattr(win32con, self.get_hive()), self.get_path(), 0, win32con.KEY_ENUMERATE_SUB_KEYS | win32con.KEY_QUERY_VALUE | win32con.KEY_READ) 133 | if self.view is None: 134 | self.keyh = win32api.RegOpenKeyEx(getattr(win32con, self.get_hive()), self.get_path(), 0, win32con.KEY_ENUMERATE_SUB_KEYS | win32con.KEY_QUERY_VALUE | ntsecuritycon.READ_CONTROL) 135 | else: 136 | if self.view == 32: 137 | self.keyh = win32api.RegOpenKeyEx(getattr(win32con, self.get_hive()), self.get_path(), 0, win32con.KEY_ENUMERATE_SUB_KEYS | win32con.KEY_QUERY_VALUE | ntsecuritycon.READ_CONTROL | win32con.KEY_WOW64_32KEY) 138 | elif self.view == 64: 139 | self.keyh = win32api.RegOpenKeyEx(getattr(win32con, self.get_hive()), self.get_path(), 0, win32con.KEY_ENUMERATE_SUB_KEYS | win32con.KEY_QUERY_VALUE | ntsecuritycon.READ_CONTROL | win32con.KEY_WOW64_64KEY) 140 | except: 141 | pass 142 | # print "Can't open: " + self.get_name() 143 | return self.keyh 144 | 145 | def get_dangerous_aces(self): 146 | try: 147 | #print "[D] File: " + self.get_name() 148 | #print "[D] ACE: " 149 | #for a in self.get_sd().get_acelist().get_dangerous_perms().get_aces(): 150 | # print a.as_text() 151 | return self.get_sd().get_acelist().get_untrusted().get_dangerous_perms().get_aces() 152 | except: 153 | return [] 154 | 155 | def is_present(self): 156 | return self.get_keyh() 157 | 158 | def get_sd(self): 159 | if self.sd is None: 160 | sd = None 161 | try: 162 | sd = win32api.RegGetKeySecurity(self.get_keyh(), win32security.DACL_SECURITY_INFORMATION | win32security.OWNER_SECURITY_INFORMATION) 163 | self.sd = wpc.conf.cache.sd('regkey', sd) 164 | #print "[D]: Got security descriptor for " + self.get_name() 165 | except: 166 | # print "WARNING: Can't get security descriptor for regkey: " + self.get_name() 167 | self.sd = None 168 | 169 | return self.sd 170 | 171 | def get_type(self): 172 | return 'regkey' 173 | 174 | def as_tab(self, dangerous_only=1): 175 | lines = [] 176 | lines.append(wpc.utils.tab_line("info", self.get_type(), str(self.get_name()))) 177 | if self.get_sd(): 178 | lines.append(wpc.utils.tab_line("gotsd", self.get_type(), str(self.get_name()), "yes")) 179 | lines.append(wpc.utils.tab_line("owner", self.get_type(), str(self.get_name()), str(self.get_sd().get_owner().get_fq_name()))) 180 | if self.get_sd().has_dacl(): 181 | lines.append(wpc.utils.tab_line("hasdacl", self.get_type(), str(self.get_name()), "yes")) 182 | if dangerous_only: 183 | lines.extend(self.get_sd().dangerous_aces_as_tab("ace", self.get_type(), str(self.get_name()))) 184 | else: 185 | lines.extend(self.get_sd().aces_as_tab("ace", self.get_type(), str(self.get_name()))) 186 | else: 187 | lines.append(wpc.utils.tab_line("hasdacl", self.get_type(), str(self.get_name()), "no")) 188 | else: 189 | lines.append(wpc.utils.tab_line("gotsd", self.get_type(), str(self.get_name()), "no")) 190 | #print lines 191 | return "\n".join(lines) -------------------------------------------------------------------------------- /wpc/report/__init__.py: -------------------------------------------------------------------------------- 1 | pass 2 | -------------------------------------------------------------------------------- /wpc/report/appendices.py: -------------------------------------------------------------------------------- 1 | import xml.etree.cElementTree as etree 2 | from wpc.report.appendix import appendix 3 | 4 | class appendices: 5 | def __init__(self): 6 | self.appendices = [] 7 | 8 | def add_appendix(self, a): 9 | self.appendices.append(a) 10 | 11 | def as_xml_string(self): 12 | return etree.tostring(self.as_xml()) 13 | 14 | def as_xml(self): 15 | r = etree.Element('appendices') 16 | for i in self.appendices: 17 | r.append(i.as_xml()) 18 | return r 19 | -------------------------------------------------------------------------------- /wpc/report/appendix.py: -------------------------------------------------------------------------------- 1 | import wpc.conf 2 | import xml.etree.cElementTree as etree 3 | 4 | class appendix: 5 | def __init__(self, title): 6 | self.title = title 7 | self.preamble = None 8 | self.table = [] 9 | self.table_name = None 10 | self.table_style = None 11 | 12 | def add_table_row(self, cells): 13 | self.table.append(map(lambda x: wpc.utils.to_printable(x), cells)) 14 | 15 | def get_title(self): 16 | return self.title 17 | 18 | def get_preamble(self): 19 | return self.preamble 20 | 21 | def set_preamble(self, preamble): 22 | self.preamble = preamble 23 | 24 | def get_table_name(self): 25 | return self.table_name 26 | 27 | def get_table_style(self): 28 | return self.table_style 29 | 30 | def set_title(self, title): 31 | self.title = title 32 | 33 | def set_table_name(self, table_name): 34 | self.table_name = table_name 35 | 36 | def set_table_style(self, table_name): 37 | self.table_style = table_name 38 | 39 | def get_table(self): 40 | return self.table 41 | 42 | def as_xml(self): 43 | appendix = etree.Element('appendix', title = self.title) 44 | preamble = etree.Element('preamble') 45 | preamble.text = self.preamble 46 | appendix.append(preamble) 47 | args = {} 48 | if self.table_name: 49 | args['name'] = self.table_name 50 | if self.table_style: 51 | args['style'] = self.table_style 52 | table = etree.Element('table', *args) 53 | for r in self.get_table(): 54 | row = etree.Element('row') 55 | for c in r: 56 | cell = etree.Element('cell') 57 | cell.text = c 58 | row.append(cell) 59 | table.append(row) 60 | appendix.append(table) 61 | return appendix 62 | 63 | -------------------------------------------------------------------------------- /wpc/report/fileAcl.py: -------------------------------------------------------------------------------- 1 | # report.issue 2 | # report.fileAcl 3 | # report.serviceFileAcl 4 | # report.serviceAcl 5 | # report.shareAcl 6 | # report.dirAcl? 7 | # report.registryKeyAcl 8 | 9 | from wpc.acelist import acelist 10 | 11 | 12 | # This is a bit like a file object, but we may be reporting only some of the ACEs from the DACL 13 | class fileAcl: 14 | def __init__(self, f, a): 15 | self.acelist = None 16 | self.filename = None 17 | self.set_filename(f) 18 | self.set_acelist(a) 19 | 20 | def set_acelist(self, aces): 21 | self.acelist = acelist() 22 | for ace in aces: 23 | self.acelist.add(ace) 24 | 25 | def set_filename(self, f): 26 | self.filename = f 27 | 28 | def get_filename(self): 29 | return self.filename 30 | 31 | def get_acelist(self): 32 | return self.acelist 33 | 34 | def as_text(self): 35 | t = '' 36 | for ace in self.get_acelist().get_aces(): 37 | t += self.get_filename() + ":\n " + ace.as_text() + "\n" 38 | return t 39 | 40 | # TODO owner? -------------------------------------------------------------------------------- /wpc/report/issueAcl.py: -------------------------------------------------------------------------------- 1 | # report.issue 2 | # report.fileAcl 3 | # report.serviceFileAcl 4 | # report.serviceAcl 5 | # report.shareAcl 6 | # report.dirAcl? 7 | # report.registryKeyAcl 8 | 9 | from wpc.acelist import acelist 10 | 11 | 12 | # This is a genric ACL that can be rendered in a report 13 | class issueAcl: 14 | def __init__(self, n, a): 15 | self.acelist = None 16 | self.name = None 17 | self.set_name(n) 18 | self.set_acelist(a) 19 | 20 | def set_acelist(self, aces): 21 | self.acelist = acelist() 22 | for ace in aces: 23 | self.acelist.add(ace) 24 | 25 | def set_name(self, n): 26 | self.name = n 27 | 28 | def get_name(self): 29 | return self.name 30 | 31 | def get_acelist(self): 32 | return self.acelist 33 | 34 | def as_text(self): 35 | t = '' 36 | for ace in self.get_acelist().get_aces(): 37 | t += self.get_name() + ":\n " + ace.as_text() + "\n" 38 | return t 39 | 40 | # TODO owner? -------------------------------------------------------------------------------- /wpc/report/issues.py: -------------------------------------------------------------------------------- 1 | from wpc.report.issue import issue 2 | import xml.etree.cElementTree as etree 3 | from lxml import etree as letree 4 | from operator import itemgetter, attrgetter, methodcaller 5 | 6 | 7 | # TODO should this class contain info about the scan? or define a new class called report? 8 | # Version of script 9 | # Date, time of audit 10 | # Who the audit ran as (username, groups, privs) 11 | # ... 12 | class issues: 13 | def __init__(self): 14 | self.issues = [] 15 | 16 | def get_by_id(self, identifier): 17 | # search for issue 18 | for i in self.issues: 19 | if i.get_id() == identifier: 20 | return i 21 | 22 | # create new issue 23 | i = issue(identifier) 24 | self.add_issue(i) 25 | return i 26 | 27 | def add_issue(self, i): 28 | self.issues.append(i) 29 | 30 | def add_supporting_data(self, identifier, k, v): 31 | self.get_by_id(identifier).add_supporting_data(k, v) 32 | 33 | def get_all(self): 34 | s = sorted(self.issues, key=methodcaller('get_confidence'), reverse=True) 35 | return sorted(s, key=methodcaller('get_severity'), reverse=True) 36 | 37 | def as_xml_string(self): 38 | return etree.tostring(self.as_xml()) 39 | 40 | def as_xml(self): 41 | r = etree.Element('issues') 42 | for i in self.get_all(): 43 | r.append(i.as_xml()) 44 | return r 45 | 46 | def as_text(self): 47 | xslt_fh = open('xsl/text.xsl', 'r') # TODO need to be able to run from other dirs too! 48 | xslt_str = xslt_fh.read() 49 | xslt_fh.close() 50 | xslt_root = letree.XML(xslt_str) 51 | transform = letree.XSLT(xslt_root) 52 | return str(transform(letree.XML(self.as_xml_string()))) 53 | 54 | def as_html(self): 55 | xslt_fh = open('xsl/html.xsl', 'r') # TODO need to be able to run from other dirs too! 56 | xslt_str = xslt_fh.read() 57 | xslt_fh.close() 58 | xslt_root = letree.XML(xslt_str) 59 | transform = letree.XSLT(xslt_root) 60 | return str(transform(letree.XML(self.as_xml_string()))) 61 | -------------------------------------------------------------------------------- /wpc/scheduledtask.py: -------------------------------------------------------------------------------- 1 | import wpc.utils 2 | 3 | class scheduledtask(): 4 | def __init__(self, name, root): 5 | self.root = root 6 | self.name = name 7 | self.author = None 8 | self.description = None 9 | self.uri = None 10 | self.source = None 11 | self.date = None 12 | self.sd_text = None 13 | self.enabled = None 14 | self.command = None 15 | self.command_args = None 16 | self.comhandler = None 17 | self.runas = None 18 | self.comhandler_data = None 19 | self.action_context = None 20 | self.command_path = None 21 | self.command_set = 0 22 | self.principal_groupid = None 23 | self.principal_userid = None 24 | self.principal_runlevel = None 25 | self.principal_id = None 26 | self.exec_command = "" 27 | self.exec_args = "" 28 | try: 29 | self.exec_command = root.Actions.Exec.Command 30 | self.exec_args = root.Actions.Exec.Arguments 31 | except: 32 | pass 33 | 34 | # source, author and description often refer to DLLs 35 | # run as: 36 | # triggers (0 or more - each of which can be enabled or not) 37 | # principals - not sure if this is same as runas 38 | 39 | 40 | def get_name(self): 41 | return self.name 42 | 43 | def get_action_context(self): 44 | if not self.action_context: 45 | try: 46 | self.action_context = self.root.Actions.attrib["Context"] 47 | except: 48 | self.action_context = "" 49 | return self.action_context 50 | 51 | def get_comhandler(self): 52 | if not self.comhandler: 53 | try: 54 | self.comhandler = self.root.Actions.ComHandler.ClassId 55 | except: 56 | self.comhandler = "" 57 | return self.comhandler 58 | 59 | def get_comhandler_data(self): 60 | if not self.comhandler_data: 61 | try: 62 | self.comhandler_data = self.root.Actions.ComHandler.Data 63 | except: 64 | self.comhandler_data = "" 65 | return self.comhandler_data 66 | 67 | def get_command(self): 68 | if not self.command: 69 | try: 70 | self.command = self.root.Actions.Exec.Command 71 | self.command_set = 1 72 | except: 73 | self.command = "" 74 | return self.command 75 | 76 | def get_command_path(self): 77 | if not self.command_path: 78 | try: 79 | self.command_path = wpc.utils.dequote(wpc.utils.env_expand(self.get_command())) 80 | #self.command_path = wpc.utils.env_expand(self.get_command()) 81 | except: 82 | self.command_path = "" 83 | if not self.command_set: 84 | return None 85 | return self.command_path 86 | 87 | def get_command_args(self): 88 | if not self.command_args: 89 | try: 90 | self.command_args = self.root.Actions.Exec.Arguments 91 | except: 92 | self.command_args = "" 93 | return self.command_args 94 | 95 | def get_author(self): 96 | if not self.author: 97 | try: 98 | self.author = self.root.RegistrationInfo.Author 99 | except: 100 | self.author = "" 101 | return self.author 102 | 103 | def get_principal_groupid(self): 104 | if not self.principal_groupid: 105 | try: 106 | self.principal_groupid = self.root.Principals.Principal.GroupId 107 | except: 108 | self.principal_groupid = "" 109 | return self.principal_groupid 110 | 111 | def get_principal_userid(self): 112 | if not self.principal_userid: 113 | try: 114 | self.principal_userid = self.root.Principals.Principal.UserId 115 | except: 116 | self.principal_userid = "" 117 | return self.principal_userid 118 | 119 | def get_principal_runlevel(self): 120 | if not self.principal_runlevel: 121 | try: 122 | self.principal_runlevel = self.root.Principals.Principal.RunLevel 123 | except: 124 | self.principal_runlevel = "" 125 | return self.principal_runlevel 126 | 127 | def get_principal_id(self): 128 | if not self.principal_id: 129 | try: 130 | self.principal_id = self.root.Principals.Principal.attrib["id"] 131 | except: 132 | self.principal_id = "" 133 | return self.principal_id 134 | 135 | def get_enabled(self): 136 | if not self.enabled: 137 | try: 138 | self.enabled = self.root.Settings.Enabled 139 | except: 140 | self.enabled = "" 141 | return self.enabled 142 | 143 | def get_date(self): 144 | if not self.date: 145 | try: 146 | self.date = self.root.RegistrationInfo.Date 147 | except: 148 | self.date = "" 149 | return self.date 150 | 151 | def get_sd_text(self): 152 | if not self.sd_text: 153 | try: 154 | self.sd_text = self.root.RegistrationInfo.SecurityDescriptor 155 | except: 156 | self.sd_text = "" 157 | return self.sd_text 158 | 159 | def get_source(self): 160 | if not self.source: 161 | try: 162 | self.source = self.root.RegistrationInfo.Source 163 | except: 164 | self.source = "" 165 | return self.source 166 | 167 | def get_description(self): 168 | if not self.description: 169 | try: 170 | self.description = self.root.RegistrationInfo.Description 171 | except: 172 | self.description = "" 173 | return self.description 174 | 175 | def get_uri(self): 176 | if not self.uri: 177 | try: 178 | self.uri = self.root.RegistrationInfo.URI 179 | except: 180 | self.uri = "" 181 | return self.uri 182 | 183 | 184 | def as_tab(self): 185 | fields = ["info", "scheduledtask"] 186 | fields.append(self.get_name()) 187 | fields.append(self.get_uri()) 188 | fields.append(self.get_source()) 189 | fields.append(self.get_author()) 190 | fields.append(self.get_description()) 191 | fields.append(self.get_date()) 192 | fields.append(self.get_enabled()) 193 | fields.append(self.get_sd_text()) 194 | fields.append(self.get_action_context()) 195 | fields.append(self.get_principal_groupid()) 196 | fields.append(self.get_principal_userid()) 197 | fields.append(self.get_principal_id()) 198 | fields.append(self.get_principal_id()) 199 | fields.append(self.get_comhandler()) 200 | fields.append(self.get_comhandler_data()) 201 | fields.append(self.get_command()) 202 | fields.append(self.get_command_path()) 203 | fields.append(self.get_command_args()) 204 | return wpc.utils.tab_line(*fields) 205 | 206 | def as_text(self): 207 | t = "" 208 | t += "----------------------\n" 209 | t += "Name: %s\n" % self.get_name() 210 | t += "URI: %s\n" % self.get_uri() 211 | t += "Source: %s\n" % self.get_source() 212 | t += "Author: %s\n" % self.get_author() 213 | t += "Description: %s\n" % self.get_description() 214 | t += "Date: %s\n" % self.get_date() 215 | t += "Enabled: %s\n" % self.get_enabled() 216 | t += "SD: %s\n" % self.get_sd_text() 217 | t += "action_context: %s\n" % self.get_action_context() 218 | t += "principal_groupid: %s\n" % self.get_principal_groupid() 219 | t += "principal_userid: %s\n" % self.get_principal_userid() 220 | t += "principal_runlevel: %s\n" % self.get_principal_id() 221 | t += "principal_id: %s\n" % self.get_principal_id() 222 | t += "comhandler: %s\n" % self.get_comhandler() 223 | t += "comhandler_data: %s\n" % self.get_comhandler_data() 224 | t += "command: %s\n" % self.get_command() 225 | t += "command_path: %s\n" % self.get_command_path() 226 | t += "command_args: %s\n" % self.get_command_args() 227 | return t 228 | 229 | -------------------------------------------------------------------------------- /wpc/scheduledtasks.py: -------------------------------------------------------------------------------- 1 | from wpc.scheduledtask import scheduledtask 2 | import re 3 | import subprocess 4 | from lxml import objectify 5 | import os 6 | 7 | class scheduledtasks(): 8 | def __init__(self): 9 | self.tasks = [] 10 | 11 | def get_all_tasks(self, **kwargs): 12 | try: 13 | content = subprocess.check_output("schtasks /query /xml", stderr = open(os.devnull, 'w')) 14 | except: 15 | print "[E] Can't run schtasks. Doesn't work < Vista. Skipping." 16 | return 0 17 | 18 | chunks = content.split("(.*)", chunk, re.MULTILINE | re.DOTALL) 27 | name = m.group(1) 28 | xml_string = m.group(2).lstrip() 29 | xml_string = xml_string.replace("UTF-16", "UTF-8") 30 | xml_string = xml_string.replace("", "") 31 | root = objectify.fromstring(xml_string) 32 | self.tasks.append(scheduledtask(name, root)) 33 | 34 | return self.tasks 35 | 36 | 37 | -------------------------------------------------------------------------------- /wpc/sd.py: -------------------------------------------------------------------------------- 1 | from wpc.ace import ace 2 | from wpc.acelist import acelist 3 | from wpc.principal import principal 4 | import win32security 5 | import wpc.conf 6 | 7 | 8 | class sd(acelist): 9 | def __init__(self, type, secdesc): 10 | self.type = type 11 | self.sd = secdesc 12 | self.owner = None 13 | self.group = None 14 | self.owner_sid = None 15 | self.group_sid = None 16 | self.dacl = None 17 | self.acelist = None 18 | self.untrusted_owner = None 19 | 20 | def get_aces(self): 21 | if self.acelist == None: 22 | self.acelist = acelist() 23 | dacl = self.get_dacl() 24 | if dacl: # Some files will have no DACL - e.g. on HGFS file systems 25 | for ace_no in range(0, self.dacl.GetAceCount()): 26 | #print "[D] ACE #%d" % ace_no 27 | self.acelist.add(ace(self.get_type(), dacl.GetAce(ace_no))) 28 | return self.acelist.get_aces() 29 | 30 | def get_acelist(self): 31 | if self.acelist == None: 32 | self.get_aces() # side effect is defining self.acelist 33 | return self.acelist 34 | 35 | def get_type(self): 36 | return self.type 37 | 38 | def dangerous_as_text(self): 39 | s = "" 40 | 41 | o = self.get_owner() 42 | if o: 43 | s += "Owner: " + self.get_owner().get_fq_name() + "\n" 44 | else: 45 | s += "Owner: [none] \n" 46 | 47 | g = self.get_group() 48 | if g: 49 | s += "Group: " + self.get_group().get_fq_name() + "\n" 50 | else: 51 | s += "Group: [none] \n" 52 | 53 | for a in self.get_aces_dangerous(): 54 | s += a.as_text() + "\n" 55 | return s 56 | 57 | def dump(self): 58 | print self.as_text() 59 | 60 | def has_no_dacl(self): 61 | if self.get_dacl(): 62 | return 0 63 | else: 64 | return 1 65 | 66 | def has_dacl(self): 67 | if self.get_dacl(): 68 | return 1 69 | else: 70 | return 0 71 | 72 | def perms_for(self, principal): 73 | # TODO use all_perms above 74 | pass 75 | 76 | def dangerous_perms_for(self): 77 | pass 78 | 79 | def dangerous_perms_for_principal(self, principal): 80 | # TODO use dangerous_perms_write above 81 | pass 82 | 83 | def writable_by(self, principals): 84 | # TODO check parent dir? 85 | # TODO use dangerous_perms_write above 86 | pass 87 | 88 | def get_sd(self): 89 | return self.sd 90 | 91 | def get_dacl(self): 92 | if self.dacl == None: 93 | self.dacl = self.get_sd().GetSecurityDescriptorDacl() 94 | return self.dacl 95 | 96 | def get_owner(self): 97 | if not self.owner: 98 | owner_sid = self.get_owner_sid() 99 | if owner_sid: 100 | self.owner = principal(self.get_owner_sid()) 101 | else: 102 | self.owner = None 103 | return self.owner 104 | 105 | def get_group(self): 106 | if not self.group: 107 | group_sid = self.get_group_sid() 108 | if group_sid: 109 | self.group = principal(self.get_group_sid()) 110 | else: 111 | self.group = None 112 | return self.group 113 | 114 | def get_group_sid(self): 115 | if self.group_sid == None: 116 | self.group_sid = self.get_sd().GetSecurityDescriptorGroup() 117 | return self.group_sid 118 | 119 | def get_owner_sid(self): 120 | if self.owner_sid == None: 121 | self.owner_sid = self.get_sd().GetSecurityDescriptorOwner() 122 | return self.owner_sid 123 | 124 | def get_remote_server(self): 125 | return wpc.conf.remote_server 126 | 127 | def get_owner_string(self): 128 | owner_name, owner_domain, type = self.get_owner_tuple() 129 | return owner_domain + "\\" + owner_name 130 | 131 | def get_owner_name(self): 132 | if self.owner_name == None: 133 | self.owner_name = win32security.ConvertSidToStringSid(self.get_owner_sid) 134 | return self.owner_name 135 | 136 | def set_owner_name(self, name): 137 | self.owner_name = name 138 | 139 | def set_owner_domain(self, name): 140 | self.owner_domain = name 141 | 142 | def get_owner_tuple(self): 143 | owner_name, owner_domain, type = wpc.conf.cache.LookupAccountSid(self.get_remote_server(), self.get_owner_sid()) 144 | self.set_owner_name(owner_name) 145 | self.set_owner_domain(owner_domain) 146 | return owner_name, owner_domain, type 147 | 148 | def owner_is_untrusted(self): 149 | if not self.untrusted_owner: 150 | self.untrusted_owner = self.get_owner().is_trusted() ^ 1 # xor 151 | return self.untrusted_owner 152 | 153 | def as_text(self): 154 | return self._as_text(0) 155 | 156 | def untrusted_as_text(self): 157 | return self._as_text(1) 158 | 159 | def aces_as_tab(self, *fields): 160 | field_list = list(fields) 161 | lines = [] 162 | for a in self.get_aces(): 163 | for perm in a.as_list(): 164 | lines.append("\t".join(field_list + perm)) 165 | return lines 166 | 167 | def dangerous_aces_as_tab(self, *fields): 168 | field_list = list(fields) 169 | lines = [] 170 | for a in self.get_acelist().get_untrusted().get_dangerous_perms().get_aces(): 171 | for perm in a.as_list(): 172 | lines.append("\t".join(field_list + perm)) 173 | return lines 174 | 175 | def _as_text(self, flag): 176 | s = "--- start %s security descriptor ---\n" % self.get_type() 177 | o = self.get_owner() 178 | if o: 179 | s += "Owner: " + self.get_owner().get_fq_name() + "\n" 180 | else: 181 | s += "Owner: [none] \n" 182 | 183 | g = self.get_group() 184 | if g: 185 | s += "Group: " + self.get_group().get_fq_name() + "\n" 186 | else: 187 | s += "Group: [none] \n" 188 | if not self.get_dacl(): 189 | s += "No DACL set!\n" 190 | for a in self.get_aces(): 191 | if flag: 192 | if not a.get_principal().is_trusted(): 193 | s += a.as_text() + "\n" 194 | else: 195 | s += a.as_text() + "\n" 196 | s += "--- end security descriptor ---\n" 197 | return s 198 | 199 | -------------------------------------------------------------------------------- /wpc/service.py: -------------------------------------------------------------------------------- 1 | from wpc.file import file as File 2 | from wpc.regkey import regkey 3 | from wpc.sd import sd 4 | import os 5 | import re 6 | import win32con 7 | import win32security 8 | import win32service 9 | import wpc.conf 10 | 11 | 12 | class service: 13 | def __init__(self, scm, short_name): 14 | self.scm = scm 15 | self.name = short_name 16 | self.sh = None # service handle 17 | self.sd = None # sd for service 18 | self.description = None 19 | self.type = None 20 | self.typename = None 21 | self.sh_read_control = None 22 | self.service_info = None 23 | self.service_config_failure_actions = None 24 | self.service_sid_type = None 25 | self.long_description = None 26 | self.exe_path = None # e.g. C:\Windows\system32\svchost.exe -k netsvcs 27 | self.exe_path_clean = None # e.g. C:\Windows\system32\svchost.exe 28 | self.exe_file = None # wpc.file for self.exe_path_clean 29 | self.status = None # started, stopped 30 | self.startup_type = None # auto, auto (delayed), manual, disabled 31 | self.run_as = None # wpc.user object for e.g. localsystem 32 | self.interactive = None # 0 or 1 33 | self.sh_query_status = None 34 | self.sh_query_config = None 35 | self.reg_key = None 36 | 37 | # We need different rights from the OpenService call for the different API calls we need to make 38 | # See http://msdn.microsoft.com/en-us/library/ms685981(v=vs.85).aspx 39 | # READ_CONTROL to call QueryServiceObjectSecurity 40 | # SERVICE_QUERY_STATUS for QueryServiceStatus 41 | # SERVICE_QUERY_CONFIG for QueryServiceConfig and QueryServiceConfig2 42 | # 43 | # We try to get a different handle for each. That way we only ask for what we need and should 44 | # get the maximum info about each service. 45 | 46 | def get_sh_query_config(self): 47 | if not self.sh_query_config: 48 | try: 49 | self.sh_query_config = win32service.OpenService(self.get_scm(), self.get_name(), win32service.SERVICE_QUERY_CONFIG) 50 | 51 | except: 52 | print "Service Perms: Unknown (Access Denied)" 53 | 54 | return self.sh_query_config 55 | 56 | def get_sh_query_status(self): 57 | if not self.sh_query_status: 58 | try: 59 | self.sh_query_status = win32service.OpenService(self.get_scm(), self.get_name(), win32service.SERVICE_QUERY_STATUS) 60 | except: 61 | pass 62 | return self.sh_query_status 63 | 64 | def get_sh_read_control(self): 65 | if not self.sh_read_control: 66 | try: 67 | self.sh_read_control = win32service.OpenService(self.get_scm(), self.get_name(), win32con.READ_CONTROL) 68 | except: 69 | pass 70 | return self.sh_read_control 71 | 72 | def get_status(self): 73 | if not self.status: 74 | try: 75 | s = win32service.QueryServiceStatus(self.get_sh_query_status()) 76 | self.status = s[1] 77 | if self.status == 1: 78 | self.status = "STOPPED" 79 | elif self.status == 4: 80 | self.status = "STARTED" 81 | except: 82 | pass 83 | return self.status 84 | 85 | def get_scm(self): 86 | return self.scm 87 | 88 | def get_sd(self): 89 | if not self.sd: 90 | # Need a handle with generic_read 91 | try: 92 | secdesc = win32service.QueryServiceObjectSecurity(self.get_sh_read_control(), win32security.OWNER_SECURITY_INFORMATION | win32security.DACL_SECURITY_INFORMATION) 93 | self.sd = sd('service', secdesc) 94 | except: 95 | print "ERROR: OpenService failed for '%s' (%s)" % (self.get_description(), self.get_name()) 96 | 97 | return self.sd 98 | 99 | def get_name(self): 100 | return self.name 101 | 102 | def get_exe_file(self): 103 | if not self.exe_file: 104 | filename = self.get_exe_path_clean() 105 | if filename: # might be None 106 | self.exe_file = File(filename) 107 | else: 108 | self.exe_file = None 109 | return self.exe_file 110 | 111 | def get_exe_path_clean(self): 112 | if not self.exe_path_clean: 113 | self.exe_path_clean = None 114 | binary_dirty = self.get_exe_path() 115 | binary_dirty = str.replace(str(binary_dirty), "/", "\\") 116 | 117 | # remove quotes and leading white space 118 | m = re.search('^[\s]*?"([^"]+)"', binary_dirty) 119 | if m and os.path.exists(m.group(1)): 120 | self.exe_path_clean = m.group(1) 121 | return self.exe_path_clean 122 | else: 123 | if m: 124 | binary_dirty = m.group(1) 125 | 126 | # Paths for drivers are written in an odd way, so we regex them 127 | re1 = re.compile(r'^\\systemroot', re.IGNORECASE) 128 | binary_dirty = re1.sub(os.getenv('SystemRoot'), binary_dirty) 129 | re2 = re.compile(r'^system32\\', re.IGNORECASE) 130 | binary_dirty = re2.sub(os.getenv('SystemRoot') + r'\\system32\\', binary_dirty) 131 | re2 = re.compile(r'^\\\?\?\\', re.IGNORECASE) 132 | binary_dirty = re2.sub('', binary_dirty) 133 | binary_dirty = binary_dirty.replace("/", "\\") # c:/foo/bar -> c:\foo\bar 134 | 135 | if os.path.exists(binary_dirty): 136 | self.exe_path_clean = binary_dirty 137 | return self.exe_path_clean 138 | 139 | chunks = binary_dirty.split(" ") 140 | candidate = "" 141 | for chunk in chunks: 142 | if candidate: 143 | candidate = candidate + " " 144 | candidate = candidate + chunk 145 | 146 | if os.path.exists(candidate) and os.path.isfile(candidate): 147 | self.exe_path_clean = candidate 148 | break 149 | 150 | if os.path.exists(candidate + ".exe") and os.path.isfile(candidate + ".exe"): 151 | self.exe_path_clean = candidate + ".exe" 152 | break 153 | 154 | if wpc.conf.on64bitwindows: 155 | candidate2 = candidate.replace("system32", "syswow64") 156 | if os.path.exists(candidate2) and os.path.isfile(candidate2): 157 | self.exe_path_clean = candidate2 158 | break 159 | 160 | if os.path.exists(candidate2 + ".exe") and os.path.isfile(candidate2 + ".exe"): 161 | self.exe_path_clean = candidate2 + ".exe" 162 | break 163 | return self.exe_path_clean 164 | 165 | def get_exe_path(self): 166 | if not self.exe_path: 167 | self.exe_path = self.get_service_info(3) 168 | return self.exe_path 169 | 170 | def get_run_as(self): 171 | if not self.run_as: 172 | self.run_as = self.get_service_info(7) 173 | return self.run_as 174 | 175 | def set_interactive(self, n): 176 | self.interactive = n 177 | 178 | # service or driver? 179 | def get_type(self): 180 | if not self.type: 181 | self.type = self.get_service_info(0) 182 | if not self.type == '[unknown]': 183 | if self.type & 0x100: 184 | self.type = self.type - 0x100 185 | self.set_interactive(1) 186 | else: 187 | self.set_interactive(0) 188 | 189 | if self.type == 1: 190 | self.type = "KERNEL_DRIVER" 191 | elif self.type == 2: 192 | self.type = "FILE_SYSTEM_DRIVER" 193 | elif self.type == 32: 194 | self.type = "WIN32_SHARE_PROCESS" 195 | elif self.type == 16: 196 | self.type = "WIN32_OWN_PROCESS" 197 | return self.type 198 | 199 | def get_type_name(self): 200 | if not self.typename: 201 | type = self.get_type() 202 | if type == "KERNEL_DRIVER": 203 | self.typename = "driver" 204 | elif type == "FILE_SYSTEM_DRIVER": 205 | self.typename = "driver" 206 | elif type == "WIN32_SHARE_PROCESS": 207 | self.typename = "service" 208 | elif type == "WIN32_OWN_PROCESS": 209 | self.typename = "service" 210 | return self.typename 211 | 212 | def get_startup_type(self): 213 | if not self.startup_type: 214 | self.startup_type = self.get_service_info(1) 215 | if self.startup_type == 2: 216 | self.startup_type = "AUTO_START" 217 | elif self.startup_type == 0: 218 | self.startup_type = "BOOT_START" 219 | elif self.startup_type == 3: 220 | self.startup_type = "DEMAND_START" 221 | elif self.startup_type == 4: 222 | self.startup_type = "DISABLED" 223 | elif self.startup_type == 1: 224 | self.startup_type = "SYSTEM_START" 225 | return self.startup_type 226 | 227 | def get_description(self): 228 | if not self.description: 229 | self.description = self.get_service_info(8) 230 | return self.description 231 | 232 | def get_service_info(self, n): 233 | if not self.service_info: 234 | try: 235 | self.service_info = win32service.QueryServiceConfig(self.get_sh_query_config()) 236 | except: 237 | pass 238 | 239 | if self.service_info: 240 | return self.service_info[n] 241 | else: 242 | return "[unknown]" 243 | 244 | def get_service_config_failure_actions(self): 245 | if not self.service_config_failure_actions: 246 | try: 247 | self.service_config_failure_actions = win32service.QueryServiceConfig2(self.get_sh_query_config(), win32service.SERVICE_CONFIG_FAILURE_ACTIONS) 248 | except: 249 | pass 250 | if not self.service_config_failure_actions: 251 | self.service_config_failure_actions = "" 252 | return self.service_config_failure_actions 253 | 254 | def get_service_sid_type(self): 255 | if not self.service_sid_type: 256 | try: 257 | self.service_sid_type = win32service.QueryServiceConfig2(self.get_sh_query_config(), win32service.SERVICE_CONFIG_SERVICE_SID_INFO) 258 | if self.service_sid_type == 0: 259 | self.service_sid_type = "SERVICE_SID_TYPE_NONE" 260 | if self.service_sid_type == 1: 261 | self.service_sid_type = "SERVICE_SID_TYPE_RESTRICTED" 262 | if self.service_sid_type == 2: 263 | self.service_sid_type = "SERVICE_SID_TYPE_UNRESTRICTED" 264 | except: 265 | pass 266 | return self.service_sid_type 267 | 268 | def get_long_description(self): 269 | if not self.long_description: 270 | try: 271 | self.long_description = win32service.QueryServiceConfig2(self.get_sh_query_config(), win32service.SERVICE_CONFIG_DESCRIPTION) 272 | except: 273 | pass 274 | if not self.long_description: 275 | self.long_description = "" 276 | return self.long_description 277 | 278 | def as_text(self): 279 | return self._as_text(0) 280 | 281 | def as_tab_delim(self): 282 | return self._as_tab_delim(0) 283 | 284 | def untrusted_as_text(self): 285 | return self._as_text(1) 286 | 287 | def _as_tab_delim(self, flag): 288 | return "%s\t%s\t%s\t%s" % (wpc.conf.remote_server, self.get_name(), self.get_run_as(), self.get_exe_path()) 289 | 290 | def _as_text(self, flag): 291 | t = "" 292 | t += "---------------------------------------\n" 293 | t += "Service: " + self.get_name() + "\n" 294 | t += "Description: " + self.get_description() + "\n" 295 | t += "Type: " + str(self.get_type()) + "\n" 296 | t += "Status: " + str(self.get_status()) + "\n" 297 | t += "Startup: " + str(self.get_startup_type()) + "\n" 298 | t += "Long Desc: " + self.removeNonAscii(self.get_long_description()) + "\n" # in case of stupid chars in desc 299 | t += "Binary: " + self.get_exe_path() + "\n" 300 | if self.get_exe_path_clean(): 301 | t += "Binary (clean): " + self.get_exe_path_clean() + "\n" 302 | else: 303 | t += "Binary (clean): [Missing Binary]\n" 304 | t += "Run as: " + self.get_run_as() + "\n" 305 | t += "Svc Sid Type: " + str(self.get_service_sid_type()) + "\n" 306 | t += "Failure Actions:%s\n" % self.get_service_config_failure_actions() 307 | t += "\n" 308 | t += "Service Security Descriptor:\n" 309 | if self.get_sd(): 310 | if flag: 311 | t += self.get_sd().untrusted_as_text() + "\n" 312 | else: 313 | t += self.get_sd().as_text() + "\n" 314 | else: 315 | t += "[unknown]\n" 316 | t += "\n" 317 | t += "Security Descriptor for Executable:" + "\n" 318 | if self.get_exe_file(): 319 | if flag: 320 | t += self.get_exe_file().get_sd().untrusted_as_text() + "\n" 321 | else: 322 | t += self.get_exe_file().get_sd().as_text() + "\n" 323 | else: 324 | t += "[unknown]\n" 325 | 326 | t += "Security Descriptor for Registry Key:" + "\n" 327 | if self.get_reg_key(): 328 | if flag: 329 | t += self.get_reg_key().get_sd().untrusted_as_text() 330 | else: 331 | t += self.get_reg_key().as_text() 332 | else: 333 | t += "[unknown]\n" 334 | 335 | t += "\n" 336 | return t 337 | return t 338 | 339 | def get_reg_key(self): 340 | if not self.reg_key: 341 | self.reg_key = regkey("HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\" + self.get_name()) 342 | return self.reg_key 343 | 344 | def removeNonAscii(self, s): 345 | return "".join(i for i in s if ord(i) < 128) 346 | 347 | def as_tab(self, dangerous_only=1): 348 | lines = [] 349 | lines.append(wpc.utils.tab_line("info", self.get_type_name(), str(self.get_name()))) 350 | if self.get_sd(): 351 | lines.append(wpc.utils.tab_line("gotsd", self.get_type_name(), str(self.get_name()), "yes")) 352 | lines.append(wpc.utils.tab_line("owner", self.get_type_name(), str(self.get_name()), str(self.get_sd().get_owner().get_fq_name()))) 353 | if self.get_sd().has_dacl(): 354 | lines.append(wpc.utils.tab_line("hasdacl", self.get_type_name(), str(self.get_name()), "yes")) 355 | if dangerous_only: 356 | lines.extend(self.get_sd().dangerous_aces_as_tab("ace", self.get_type_name(), str(self.get_name()))) 357 | else: 358 | lines.extend(self.get_sd().aces_as_tab("ace", self.get_type_name(), str(self.get_name()))) 359 | else: 360 | lines.append(wpc.utils.tab_line("hasdacl", self.get_type_name(), str(self.get_name()), "no")) 361 | else: 362 | lines.append(wpc.utils.tab_line("gotsd", self.get_type_name(), str(self.get_name()), "no")) 363 | #print lines 364 | return "\n".join(lines) -------------------------------------------------------------------------------- /wpc/services.py: -------------------------------------------------------------------------------- 1 | from wpc.service import service 2 | import win32service 3 | import wpc.conf 4 | 5 | 6 | class services: 7 | def __init__(self): 8 | self.scm = None 9 | self.type = win32service.SERVICE_WIN32 10 | self.services = [] 11 | self.get_services() 12 | 13 | def add(self, s): 14 | self.services.append(s) 15 | 16 | def get_type(self): 17 | return self.type 18 | 19 | def add_all(self): 20 | for s in win32service.EnumServicesStatus(self.get_scm(), self.get_type(), win32service.SERVICE_STATE_ALL): 21 | short_name = s[0] 22 | self.add(service(self.get_scm(), short_name)) 23 | 24 | def get_services(self): 25 | # populate self.services with a complete list of services if we haven't already 26 | if self.services == []: 27 | self.add_all() 28 | 29 | return self.services 30 | 31 | def get_scm(self): 32 | if not self.scm: 33 | self.scm = win32service.OpenSCManager(self.get_remote_server(), None, win32service.SC_MANAGER_ENUMERATE_SERVICE) 34 | return self.scm 35 | 36 | def get_remote_server(self): 37 | return wpc.conf.remote_server 38 | 39 | def get_services_by_user_perm(self, user, perm): 40 | # list of services that "user" can do "perm" to (e.g. start, reconfigure) 41 | pass 42 | 43 | def get_services_by_run_as(self, run_as): 44 | # list of wpc.service objects that run as "run_as" 45 | pass 46 | 47 | 48 | class drivers(services): 49 | def __init__(self): 50 | self.type = win32service.SERVICE_DRIVER 51 | self.scm = None 52 | self.services = [] 53 | self.get_services() 54 | -------------------------------------------------------------------------------- /wpc/share.py: -------------------------------------------------------------------------------- 1 | from wpc.file import file as File 2 | from wpc.sd import sd 3 | import win32net 4 | import wpc.conf 5 | import pywintypes 6 | 7 | 8 | class share: 9 | def __init__(self, name): 10 | self.name = name 11 | self.info = None 12 | self.description = None 13 | self.passwd = None 14 | self.current_uses = None 15 | self.max_uses = None 16 | self.path = None 17 | self.type = None 18 | self.sd = None 19 | self.permissions = None 20 | 21 | def get_name(self): 22 | return self.name 23 | 24 | def get_info(self): 25 | if not self.info: 26 | try: 27 | # For interactive users (users who are logged on locally to the machine), no special 28 | # group membership is required to execute the NetShareGetInfo function. For non-interactive 29 | # users, Administrator, Power User, Print Operator, or Server Operator group membership is 30 | # required to successfully execute the NetShareEnum function at levels 2, 502, and 503. No 31 | # special group membership is required for level 0 or level 1 calls. 32 | shareinfo = win32net.NetShareGetInfo(wpc.conf.remote_server, self.get_name(), 502) 33 | #print shareinfo 34 | self.description = shareinfo['reserved'] 35 | self.passwd = shareinfo['passwd'] 36 | self.current_uses = shareinfo['current_uses'] 37 | self.max_uses = shareinfo['max_uses'] 38 | 39 | if shareinfo['path']: 40 | # self.path = File(shareinfo['path']) 41 | #else: 42 | self.path = shareinfo['path'] 43 | 44 | self.type = shareinfo['type'] 45 | 46 | if shareinfo['security_descriptor']: 47 | self.sd = sd('share', shareinfo['security_descriptor']) 48 | else: 49 | self.sd = None 50 | 51 | self.permissions = shareinfo['permissions'] 52 | 53 | self.info = shareinfo 54 | except pywintypes.error as e: 55 | print "[E] %s: %s" % (e[1], e[2]) 56 | try: 57 | shareinfo = win32net.NetShareGetInfo(wpc.conf.remote_server, self.get_name(), 501) 58 | self.description = shareinfo['remark'] 59 | self.type = shareinfo['type'] 60 | self.flags = shareinfo['flags'] 61 | self.info = shareinfo 62 | except pywintypes.error as e: 63 | print "[E] %s: %s" % (e[1], e[2]) 64 | return self.info 65 | 66 | def get_description(self): 67 | if not self.description: 68 | self.get_info() 69 | 70 | return self.description 71 | 72 | def get_path(self): 73 | if not self.path: 74 | self.get_info() 75 | 76 | return self.path 77 | 78 | def get_passwd(self): 79 | if not self.passwd: 80 | self.get_info() 81 | 82 | return self.passwd 83 | 84 | def get_current_uses(self): 85 | if not self.current_uses: 86 | self.get_info() 87 | 88 | return self.current_uses 89 | 90 | def get_max_uses(self): 91 | if not self.max_uses: 92 | self.get_info() 93 | 94 | return self.max_uses 95 | 96 | # Ignore this. 97 | # "Note that Windows does not support share-level security." 98 | # http://msdn.microsoft.com/en-us/library/bb525410(v=vs.85).aspx 99 | def get_permissions(self): 100 | if not self.permissions: 101 | self.get_info() 102 | 103 | return self.permissions 104 | 105 | def get_sd(self): 106 | if not self.sd: 107 | self.get_info() 108 | 109 | return self.sd 110 | 111 | def as_text(self): 112 | t = '--- start share ---\n' 113 | t += 'Share Name: ' + str(self.get_name()) + '\n' 114 | t += 'Description: ' + str(self.get_description()) + '\n' 115 | if self.get_path(): 116 | t += 'Path: ' + str(self.get_path()) + '\n' 117 | else: 118 | t += 'Path: None\n' 119 | t += 'Passwd: ' + str(self.get_passwd()) + '\n' 120 | t += 'Current Uses: ' + str(self.get_current_uses()) + '\n' 121 | t += 'Max Uses: ' + str(self.get_max_uses()) + '\n' 122 | t += 'Permissions: ' + str(self.get_permissions()) + '\n' 123 | 124 | if self.get_path(): 125 | f = File(self.get_path()) 126 | if f.exists(): 127 | if f.get_sd(): 128 | t += 'Directory Security Descriptor:\n' 129 | t += f.get_sd().as_text() + '\n' 130 | else: 131 | t += 'Directory Security Descriptor: None (can\'t read sd)\n' 132 | else: 133 | t += 'Directory Security Descriptor: None (path doesn\'t exist)\n' 134 | else: 135 | t += 'Directory Security Descriptor: None (no path)\n' 136 | 137 | if self.get_sd(): 138 | t += 'Share Security Descriptor:\n' 139 | t += self.get_sd().as_text() + '\n' 140 | else: 141 | t += 'Share Security Descriptor: None\n' 142 | 143 | t += '--- end share ---\n' 144 | return t 145 | 146 | def as_tab(self, dangerous_only=1): 147 | lines = [] 148 | lines.append(wpc.utils.tab_line("info", "share", str(self.get_name()), str(self.get_description()), str(self.get_path()), str(self.get_passwd()), str(self.get_current_uses()), str(self.get_max_uses()))) 149 | if self.get_sd(): 150 | lines.append(wpc.utils.tab_line("gotsd", "share", str(self.get_name()), "yes")) 151 | lines.append(wpc.utils.tab_line("owner", "share", str(self.get_name()), str(self.get_sd().get_owner().get_fq_name()))) 152 | if self.get_sd().has_dacl(): 153 | lines.append(wpc.utils.tab_line("hasdacl", "share", str(self.get_name()), "yes")) 154 | if dangerous_only: 155 | lines.extend(self.get_sd().dangerous_aces_as_tab("ace", "share", str(self.get_name()))) 156 | else: 157 | lines.extend(self.get_sd().aces_as_tab("ace", "share", str(self.get_name()))) 158 | else: 159 | lines.append(wpc.utils.tab_line("hasdacl", "share", str(self.get_name()), "no")) 160 | else: 161 | lines.append(wpc.utils.tab_line("gotsd", "share", str(self.get_name()), "no")) 162 | #print lines 163 | return "\n".join(lines) -------------------------------------------------------------------------------- /wpc/shares.py: -------------------------------------------------------------------------------- 1 | from wpc.share import share 2 | import win32net 3 | import wpc.conf 4 | 5 | 6 | class shares: 7 | def __init__(self): 8 | self.shares = [] 9 | pass 10 | 11 | def get_all(self): 12 | if self.shares == []: 13 | resume = 1; 14 | while resume: 15 | resume = 0 16 | sharelist = None 17 | try: 18 | (sharelist, total, resume) = win32net.NetShareEnum(wpc.conf.remote_server, 0, resume, 9999) 19 | except: 20 | print "[E] Can't check shares - not enough privs?" 21 | 22 | if sharelist: 23 | for shareitem in sharelist: 24 | s = share(shareitem['netname']) 25 | self.shares.append(s) 26 | 27 | return self.shares -------------------------------------------------------------------------------- /wpc/softwarepackage.py: -------------------------------------------------------------------------------- 1 | import wpc.utils 2 | import re 3 | 4 | class softwarepackage(): 5 | def __init__(self, packagekey): 6 | self.packagekey = packagekey 7 | self.name = wpc.utils.to_printable(packagekey.get_value("DisplayName")) 8 | self.publisher = wpc.utils.to_printable(packagekey.get_value("Publisher")) 9 | self.version = wpc.utils.to_printable(packagekey.get_value("DisplayVersion")) 10 | self.date = wpc.utils.to_printable(packagekey.get_value("InstallDate")) 11 | self.is64bit = 0 12 | self.is32bit = 1 13 | self.bad_version = None 14 | if packagekey.get_view() and packagekey.get_view() == 64: 15 | self.is64bit = 1 16 | self.is32bit = 0 17 | 18 | def get_name(self): 19 | return self.name 20 | 21 | def get_publisher(self): 22 | return self.publisher 23 | 24 | def get_version(self): 25 | return self.version 26 | 27 | def get_arch(self): 28 | if self.is32bit: 29 | return 32 30 | return 64 31 | 32 | def get_date(self): 33 | return self.date 34 | 35 | def get_bad_version(self): 36 | return self.bad_version 37 | 38 | def is_of_type(self, sw_category): 39 | if sw_category in wpc.conf.software.keys(): 40 | for sw_prefix in wpc.conf.software[sw_category]['names']: 41 | if self.get_name().lower().find(sw_prefix.lower()) == 0: 42 | return 1 43 | return 0 44 | 45 | def is_vulnerable_version(self): 46 | version = self.get_version() 47 | for vuln_info in wpc.conf.vulnerable_software_version_info: 48 | if 'installed_package_re' in vuln_info: 49 | m = re.search(vuln_info['installed_package_re'], self.get_name()) 50 | if not m: 51 | continue 52 | 53 | if 'installed_vendor_re' in vuln_info: 54 | m = re.search(vuln_info['installed_vendor_re'], self.get_publisher()) 55 | if not m: 56 | continue 57 | 58 | if not vuln_info['installed_version_string_ok']: 59 | if 'version_from_name_re' in vuln_info: 60 | version = re.sub(vuln_info['version_from_name_re']['from_re'], vuln_info['version_from_name_re']['to_re'], self.get_name()) 61 | 62 | self.bad_version = version 63 | if wpc.utils.version_less_than_or_equal_to(version, vuln_info['newest_vulnerable_version']): 64 | return 1 65 | 66 | return 0 67 | -------------------------------------------------------------------------------- /wpc/softwarepackages.py: -------------------------------------------------------------------------------- 1 | from wpc.regkey import regkey 2 | from wpc.softwarepackage import softwarepackage 3 | import wpc.conf 4 | import re 5 | 6 | class softwarepackages(): 7 | def __init__(self): 8 | self.packages = [] 9 | 10 | 11 | def get_installed_packages(self): 12 | print '[+] Checking installed software' 13 | uninstall = regkey('HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Uninstall') 14 | self.packages = self._get_packages_from_key(uninstall) 15 | 16 | if wpc.conf.on64bitwindows: 17 | print '[+] Checking installed software (WoW64 enabled)' 18 | uninstall = regkey('HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Uninstall', view=64) 19 | self.packages = self.packages + self._get_packages_from_key(uninstall) 20 | 21 | return self.packages 22 | 23 | def _get_packages_from_key(self, uninstall): 24 | packages = [] 25 | if uninstall.is_present(): 26 | for subkey in uninstall.get_subkeys(): 27 | name = wpc.utils.to_printable(subkey.get_value("DisplayName")) 28 | if not name is None: 29 | packages.append(softwarepackage(subkey)) 30 | return packages 31 | 32 | 33 | def get_software_types(self): 34 | return wpc.conf.software.keys() 35 | 36 | 37 | def get_software_of_type(self, sw_type): 38 | packages = [] 39 | for package in self.get_installed_packages(): 40 | if package.is_of_type(sw_type): 41 | packages.append(package) 42 | return packages 43 | 44 | 45 | def get_vulnerable_software(self): 46 | packages = [] 47 | for package in self.get_installed_packages(): 48 | if package.is_vulnerable_version(): 49 | packages.append(package) 50 | return packages -------------------------------------------------------------------------------- /wpc/thread.py: -------------------------------------------------------------------------------- 1 | from wpc.file import file as File 2 | from wpc.sd import sd 3 | from wpc.token import token 4 | import win32api 5 | import win32con 6 | import win32security 7 | import wpc.utils 8 | import ctypes 9 | 10 | OpenThread = ctypes.windll.kernel32.OpenThread 11 | #OpenThreadToken = ctypes.windll.advapi32.OpenThreadToken 12 | 13 | class thread: 14 | def __init__(self, tid): 15 | self.tid = tid 16 | self.th = None 17 | self.tth = None 18 | self.token = None 19 | self.sd = None 20 | self.parent_process = None 21 | 22 | def get_tid(self): 23 | return self.tid 24 | 25 | def set_parent_process(self, p): 26 | self.parent_process = p 27 | 28 | def get_parent_process(self): 29 | return self.parent_process 30 | 31 | def get_sd(self): 32 | #print "[D] get_sd passed th: %s" % self.get_th() 33 | if not self.sd: 34 | try: 35 | secdesc = win32security.GetSecurityInfo(self.get_th(), win32security.SE_KERNEL_OBJECT, win32security.DACL_SECURITY_INFORMATION | win32security.OWNER_SECURITY_INFORMATION | win32security.GROUP_SECURITY_INFORMATION) 36 | #print "[D] secdesc: %s" % secdesc 37 | self.sd = sd('thread', secdesc) 38 | except: 39 | pass 40 | #print "[D] get_sd returning: %s" % self.sd 41 | return self.sd 42 | 43 | def get_th(self): 44 | if not self.th: 45 | try: 46 | # THREAD_ALL_ACCESS needed to get security descriptor 47 | self.th = OpenThread(win32con.MAXIMUM_ALLOWED, False, self.get_tid()) 48 | #print "Openthread with THREAD_ALL_ACCESS: Success" 49 | except: 50 | try: 51 | # THREAD_VM_READ is required to list modules (DLLs, EXE) 52 | self.th = OpenThread(win32con.THREAD_VM_READ | win32con.THREAD_QUERY_INFORMATION, False, self.get_tid()) 53 | #print "Openthread with VM_READ and THREAD_QUERY_INFORMATION: Success" 54 | except: 55 | #print "Openthread with VM_READ and THREAD_QUERY_INFORMATION: Failed" 56 | try: 57 | # We can still get some info without THREAD_VM_READ 58 | self.th = OpenThread(win32con.THREAD_QUERY_INFORMATION, False, self.get_tid()) 59 | #print "Openthread with THREAD_QUERY_INFORMATION: Success" 60 | except: 61 | #print "Openthread with THREAD_QUERY_INFORMATION: Failed" 62 | try: 63 | # If we have to resort to using THREAD_QUERY_LIMITED_INFORMATION, the thread is protected. 64 | # There's no point trying THREAD_VM_READ 65 | # Ignore pydev warning. We define this at runtime because win32con is out of date. 66 | self.th = OpenThread(win32con.THREAD_QUERY_LIMITED_INFORMATION, False, self.get_tid()) 67 | #print "Openthread with THREAD_QUERY_LIMITED_INFORMATION: Success" 68 | except: 69 | #print "Openthread with THREAD_QUERY_LIMITED_INFORMATION: Failed" 70 | self.th = None 71 | # self.th = win32api.PyHANDLE(self.th) 72 | #print "[D] get_th: %s" % self.th 73 | return self.th 74 | 75 | def get_tth(self): 76 | if not self.tth: 77 | import sys 78 | import pywintypes 79 | try: 80 | self.tth = win32security.OpenThreadToken(self.get_th(), win32con.MAXIMUM_ALLOWED, True) 81 | except pywintypes.error as e: 82 | #print sys.exc_info()[0] 83 | #print "xxx" 84 | #print "[E] %s: %s" % (e[1], e[2]) 85 | pass 86 | # try: 87 | # self.tth = win32security.OpenThreadToken(self.get_th(), win32con.TOKEN_READ, True) 88 | # except: 89 | # try: 90 | # self.tth = win32security.OpenThreadToken(self.get_th(), win32con.TOKEN_QUERY, True) 91 | #print "OpenthreadToken with TOKEN_QUERY: Failed" 92 | # except: 93 | # pass 94 | # print "[D] TTH: %s" % self.tth 95 | return self.tth 96 | 97 | def get_token(self): 98 | if not self.token: 99 | if self.get_tth(): 100 | self.token = token(self.get_tth()) 101 | #print "thread get_token: %s" % self.token 102 | return self.token 103 | 104 | def as_text(self): 105 | t = '' 106 | t += "-------------------------------------------------\n" 107 | t += "TID: " + str(self.get_tid()) + "\n" 108 | t += "\nThread Security Descriptor:\n" 109 | if self.get_sd(): 110 | t += self.get_sd().as_text() 111 | 112 | t += "\nThread Access Token:\n" 113 | tok = self.get_token() 114 | if tok: 115 | t += "Thread token found:\n" 116 | t += tok.as_text() 117 | else: 118 | t += "[None - thread not impersonating]\n" 119 | 120 | return t 121 | 122 | def get_type(self): 123 | return 'thread' 124 | 125 | def as_tab(self, dangerous_only=1): 126 | lines = [] 127 | 128 | th_int = "" 129 | if self.get_token(): 130 | th_int = self.get_token().get_th_int() 131 | lines.append(self.get_token().as_tab()) 132 | 133 | lines.append(wpc.utils.tab_line("info", self.get_type(), self.get_tid(), th_int)) 134 | 135 | if self.get_sd(): 136 | lines.append(wpc.utils.tab_line("gotsd", self.get_type(), str(self.get_tid()), "yes")) 137 | lines.append(wpc.utils.tab_line("owner", self.get_type(), str(self.get_tid()), str(self.get_sd().get_owner().get_fq_name()))) 138 | if self.get_sd().has_dacl(): 139 | lines.append(wpc.utils.tab_line("hasdacl", self.get_type(), str(self.get_tid()), "yes")) 140 | if dangerous_only: 141 | lines.extend(self.get_sd().dangerous_aces_as_tab("ace", self.get_type(), str(self.get_tid()))) 142 | else: 143 | lines.extend(self.get_sd().aces_as_tab("ace", self.get_type(), str(self.get_tid()))) 144 | else: 145 | lines.append(wpc.utils.tab_line("hasdacl", self.get_type(), str(self.get_tid()), "no")) 146 | else: 147 | lines.append(wpc.utils.tab_line("gotsd", self.get_type(), str(self.get_tid()), "no")) 148 | 149 | return "\n".join(lines) -------------------------------------------------------------------------------- /wpc/user.py: -------------------------------------------------------------------------------- 1 | import wpc.conf 2 | from wpc.principal import principal 3 | import win32net 4 | 5 | 6 | # These have properties such as active, workstations that groups don't have 7 | class user(principal): 8 | def __init__(self, *args, **kwargs): 9 | principal.__init__(self, *args, **kwargs) 10 | self.member_of = [] 11 | self.effective_privileges = [] 12 | 13 | # populate principal.info['member_of'] (groups this user belongs to) 14 | # self.add_info({'member_of': " ".join(self.get_groups_fq_name())}) 15 | 16 | # populate principal.info['privileges'] (privs of user + privs of user's groups) 17 | # self.add_info({'privileges': " ".join(self.get_effective_privileges())}) 18 | 19 | def get_effective_privileges(self): 20 | if self.effective_privileges: 21 | return self.effective_privileges 22 | 23 | gprivileges = [] 24 | for g in self.get_groups(): 25 | gprivileges = list(list(gprivileges) + list(g.get_privileges())) 26 | 27 | return sorted(list(set(list(self.get_privileges()) + list(gprivileges)))) 28 | 29 | def get_groups_fq_name(self): 30 | if not self.member_of: 31 | self.member_of = self.get_groups() 32 | 33 | return map(lambda x: x.get_fq_name(), self.member_of) 34 | 35 | def get_flags(self): 36 | flags = 0 37 | info = win32net.NetUserGetInfo(wpc.conf.remote_server, self.get_name(), 1) 38 | if info['flags']: 39 | flags = info['flags'] 40 | return flags 41 | 42 | def get_password_age(self): 43 | password_age = 0 44 | info = win32net.NetUserGetInfo(wpc.conf.remote_server, self.get_name(), 1) 45 | if info['password_age']: 46 | password_age = info['password_age'] 47 | return password_age 48 | 49 | def get_groups(self): 50 | if self.member_of: 51 | return self.member_of 52 | 53 | from wpc.group import group as Group # we have to import here to avoid circular import 54 | 55 | g1 = [] 56 | g2 = [] 57 | 58 | try: 59 | g1 = win32net.NetUserGetLocalGroups(wpc.conf.remote_server, self.get_name(), 0) 60 | except: 61 | pass 62 | try: 63 | g2 = win32net.NetUserGetGroups(wpc.conf.remote_server, self.get_name()) 64 | except: 65 | pass 66 | for g in g2: 67 | g1.append(g[0]) 68 | for group in g1: 69 | gsid, s, i = wpc.conf.cache.LookupAccountName(wpc.conf.remote_server, group) 70 | self.member_of.append(Group(gsid)) 71 | 72 | return self.member_of 73 | -------------------------------------------------------------------------------- /wpc/users.py: -------------------------------------------------------------------------------- 1 | from wpc.user import user 2 | import win32net 3 | import wpc.conf 4 | import pywintypes 5 | 6 | 7 | class users(): 8 | def __init__(self): 9 | self.users = [] 10 | 11 | def get_filtered(self, ): 12 | if self.users == []: 13 | try: 14 | level = 1 15 | resume = 0 16 | while True: 17 | userlist, total, resume = win32net.NetUserEnum(wpc.conf.remote_server, level, 0, resume, 999999) 18 | #print u 19 | for u in userlist: 20 | # self.users.append(user['name']) 21 | #try: 22 | sid, name, type = wpc.conf.cache.LookupAccountName(wpc.conf.remote_server, u['name']) 23 | self.users.append(user(sid)) 24 | #except: 25 | # print "[E] failed to lookup sid of %s" % user['name'] 26 | if resume == 0: 27 | break 28 | except pywintypes.error as e: 29 | print "[E] %s: %s" % (e[1], e[2]) 30 | return self.users 31 | 32 | def get_all(self): 33 | if self.users == []: 34 | try: 35 | level = 0 36 | resume = 0 37 | while True: 38 | userlist, total, resume = win32net.NetUserEnum(wpc.conf.remote_server, level, 0, resume, 999999) 39 | #print u 40 | for u in userlist: 41 | # self.users.append(user['name']) 42 | #try: 43 | sid, name, type = wpc.conf.cache.LookupAccountName(wpc.conf.remote_server, u['name']) 44 | self.users.append(user(sid)) 45 | #except: 46 | # print "[E] failed to lookup sid of %s" % user['name'] 47 | if resume == 0: 48 | break 49 | except pywintypes.error as e: 50 | print "[E] %s: %s" % (e[1], e[2]) 51 | return self.users 52 | -------------------------------------------------------------------------------- /xsl/html.xsl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | 50 | 51 | 52 |
53 |

Windows Privilege Escalation Report

Audit of Host:

54 |
55 | 56 | 57 | 58 |

Contents

59 | 60 |

<a href="#"></a>

61 |
62 | 63 |

Information about this Audit

64 |

This report was generated on by version of windows-privesc-check.

65 |

The audit was run as the user .

66 |

The following table provides information about this audit:

67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 90 | 91 |
Hostname
Domain/Workgroup
Operating System ()
IP Addresses 86 |
    87 |
  • 88 |
89 |
92 | 93 | 94 |

Escalation Vectors

95 | 96 |
97 |

<a name=""></a>

98 | 99 | 100 | 101 | 104 | 108 | 109 | 110 |
102 | 103 | 105 |

106 | 107 |
111 | 112 |
113 |
114 | 115 | 116 |
117 | 118 | 119 |

120 | 121 |
    122 | 123 |
  • 124 |
    125 |
126 |
127 |
128 | 129 |
-------------------------------------------------------------------------------- /xsl/text.xsl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | ------------------------------------------------------------------ 9 | 10 | Title: 11 | 12 | 13 | 14 | [ ] 15 | 16 | 17 | 18 | 19 | True 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | True 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | --------------------------------------------------------------------------------