diff --git a/.cspell.json b/.cspell.json new file mode 100644 index 0000000..6cfef0e --- /dev/null +++ b/.cspell.json @@ -0,0 +1,53 @@ +{ + "import": [ + "@cspell/dict-powershell/cspell-ext.json", + "@cspell/dict-csharp/cspell-ext.json" + ], + + "version": "0.2", + "language": "en", + + "words": [ + "RSAT", + "Artefacts", + "NTDS", + "Hildreth" + ], + + "ignoreWords": [ + "malconfiguration", + "sarif", + "psscriptanalyzer", + "DEVSKIM", + "KICS", + "pscustomobject", + "certutil", + "certsvc", + "setreg", + "contoso", + "SCHANNEL", + "RSATAD", + "Dism", + "getreg", + "Dacl", + "Calver" + ], + + "flagWords": [ + ], + + "patterns": [ + { + "name": "ALL-CAPS-WORDS", + "pattern": "/\b[A-Z0-9]+\b/g", + "description": "Any word in ALL CAPS." + } + ], + + "ignoreRegExpList": [ + "ALL-CAPS-WORDS", + "Email", + "github.com/", + "@" + ] +} diff --git a/.github/workflows/Create External Help.yml b/.github/workflows/Create External Help.yml new file mode 100644 index 0000000..3312c2a --- /dev/null +++ b/.github/workflows/Create External Help.yml @@ -0,0 +1,32 @@ +name: 📚 Create External Help + +on: + pull_request: + #push: + workflow_dispatch: + +jobs: + package_help: + # The New-ExternalHelpCab cmdlet uses makecab, which depends on Windows. + runs-on: windows-latest + steps: + - name: ✅ Checkout Repository + uses: actions/checkout@v4 + - name: 📁 Display the Path + shell: pwsh + run: echo ${env:PATH} + - name: 🔢 Display the Version + shell: pwsh + run: $PSVersionTable + - name: 📖 Create and Package External PowerShell Help + shell: pwsh + run: | + Install-Module -Name PlatyPS -Scope CurrentUser -Force -SkipPublisherCheck + Import-Module -Name PlatyPS -Force + #Copy-Item ".\Help\en-US\Locksmith-help.xml" ".\Help\en-US" + $params = @{ + CabFilesFolder = ".\en-US" + LandingPagePath = ".\Docs\Locksmith.md" + OutputFolder = ".\en-US" + } + New-ExternalHelpCab @params diff --git a/.github/workflows/Deploy MkDocs.yml b/.github/workflows/Deploy MkDocs.yml new file mode 100644 index 0000000..ff093c7 --- /dev/null +++ b/.github/workflows/Deploy MkDocs.yml @@ -0,0 +1,59 @@ +name: 📖 Deploy MkDocs to GitHub +# Install, build, and deploy MkDocs to GitHub Pages using content from the Docs folder. + +on: + push: + branches: + - main # The branch you want to deploy from + paths: # Only deploy MkDocs when the contents of the docs folder change or when this workflow changes. + - 'Docs/**' + - '.github/workflows/Deploy MkDocs.yml' + - './mkdocs.yml' + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: ✅ Checkout Repository + uses: actions/checkout@v4 + + - name: 🐍 Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.x' # specify the Python version + + - name: ➕ Install Dependencies + run: | + python -m pip install --upgrade pip + pip install mkdocs mkdocs-material + + - name: 👷‍♂️ Build & Deploy MkDocs + run: | + mkdocs build + mkdocs gh-deploy --force + + # Combine markdown files to create the MkDocs index and the repository readme file. + - name: 📖 Update Index & Readme + shell: pwsh + run: | + Write-Output 'Updating Docs\Index.md & \Readme.md' + Copy-Item ./README.md ./docs/index.md + # [int16]$LineNumber = (Select-String -Path '.\docs\index.md' -Pattern 'Summary' -List).LineNumber + 1 + # $IndexTop = Get-Content -Path ./docs/index.md -TotalCount $LineNumber + # $ModuleContent = Get-Content -Path ./docs/Locksmith.md | Select-Object -Skip 12 + # $FooterContent = "`n`n" + # $CombinedContent = $IndexTop + $ModuleContent + $FooterContent + # $CombinedContent | Set-Content -Path ./docs/index.md + # $ModuleContent = $ModuleContent.Replace( '](' , '](./docs/' ) + # $CombinedContent = $IndexTop + $ModuleContent + # $CombinedContent | Set-Content -Path ./README.md + # Copy-Item ./docs/index.md ./README.md + + # NOTE: git-auto-commit-action only runs on Linux-based platforms. + #- name: 💾 Commit Changes + # uses: stefanzweifel/git-auto-commit-action@v5 + # with: + # commit_message: 'Copy MkDocs index to README' + # file_pattern: 'docs/index.md README.md' diff --git a/.github/workflows/mega-linter.yml b/.github/workflows/mega-linter.yml index a9cdd07..b0fa242 100644 --- a/.github/workflows/mega-linter.yml +++ b/.github/workflows/mega-linter.yml @@ -3,16 +3,16 @@ --- name: MegaLinter - # Trigger mega-linter at every push. Action will also be visible from Pull Requests to main + # Trigger mega-linter at every pull request. on: # Comment this line to trigger action only on pull-requests # (not recommended if you don't pay for GH Actions) # push: pull_request: - branches: + # branches: # - main - - testing + # - testing # - dev # - experimental @@ -44,9 +44,9 @@ # Give the default GITHUB_TOKEN write permission to commit and push, comment # issues & post new PR; remove the ones you do not need permissions: - contents: write - issues: write - pull-requests: write + contents: read + issues: read + pull-requests: read steps: @@ -58,7 +58,7 @@ # If you use VALIDATE_ALL_CODEBASE = true, you can remove this line to # improve performance - fetch-depth: 0 + # fetch-depth: 0 # MegaLinter - name: MegaLinter @@ -66,7 +66,7 @@ # You can override MegaLinter flavor used to have faster performances # More info at https://megalinter.io/flavors/ # The dotnet flavor includes PowerShell, MD, YAML, JSON, spelling, and more. - uses: oxsecurity/megalinter/flavors/dotnet@v7.7.0 + uses: oxsecurity/megalinter/flavors/dotnet@v8.3.0 id: ml @@ -78,7 +78,7 @@ # main. Override with true if you always want to lint all sources # # To validate the entire codebase, set to: - # VALIDATE_ALL_CODEBASE: true + VALIDATE_ALL_CODEBASE: true # # To validate only diff with main, set to: # VALIDATE_ALL_CODEBASE: >- @@ -86,11 +86,11 @@ # github.event_name == 'push' && # contains(fromJSON('["refs/heads/main", "refs/heads/master"]'), github.ref) # }} - VALIDATE_ALL_CODEBASE: >- - ${{ - github.event_name == 'push' && - contains(fromJSON('["refs/heads/main", "refs/heads/master"]'), github.ref) - }} + # VALIDATE_ALL_CODEBASE: >- + # ${{ + # github.event_name == 'push' && + # contains(fromJSON('["refs/heads/main", "refs/heads/master"]'), github.ref) + # }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -113,69 +113,3 @@ path: | megalinter-reports mega-linter.log - - # Set APPLY_FIXES_IF var for use in future steps - - name: Set APPLY_FIXES_IF var - run: | - printf 'APPLY_FIXES_IF=%s\n' "${{ - steps.ml.outputs.has_updated_sources == 1 && - ( - env.APPLY_FIXES_EVENT == 'all' || - env.APPLY_FIXES_EVENT == github.event_name - ) && - ( - github.event_name == 'push' || - github.event.pull_request.head.repo.full_name == github.repository - ) - }}" >> "${GITHUB_ENV}" - - # Set APPLY_FIXES_IF_* vars for use in future steps - - name: Set APPLY_FIXES_IF_* vars - run: | - printf 'APPLY_FIXES_IF_PR=%s\n' "${{ - env.APPLY_FIXES_IF == 'true' && - env.APPLY_FIXES_MODE == 'pull_request' - }}" >> "${GITHUB_ENV}" - printf 'APPLY_FIXES_IF_COMMIT=%s\n' "${{ - env.APPLY_FIXES_IF == 'true' && - env.APPLY_FIXES_MODE == 'commit' && - (!contains(fromJSON('["refs/heads/main", "refs/heads/master"]'), github.ref)) - }}" >> "${GITHUB_ENV}" - - # Create pull request if applicable - # (for now works only on PR from same repository, not from forks) - - name: Create Pull Request with applied fixes - uses: peter-evans/create-pull-request@v5 - id: cpr - if: env.APPLY_FIXES_IF_PR == 'true' - with: - token: ${{ secrets.PAT || secrets.GITHUB_TOKEN }} - commit-message: "[MegaLinter] Apply linters automatic fixes" - title: "[MegaLinter] Apply linters automatic fixes" - labels: bot - - - name: Create PR output - if: env.APPLY_FIXES_IF_PR == 'true' - run: | - echo "PR Number - ${{ steps.cpr.outputs.pull-request-number }}" - echo "PR URL - ${{ steps.cpr.outputs.pull-request-url }}" - - # Push new commit if applicable - # (for now works only on PR from same repository, not from forks) - - name: Prepare commit - if: env.APPLY_FIXES_IF_COMMIT == 'true' - run: sudo chown -Rc $UID .git/ - - - name: Commit and push applied linter fixes - uses: stefanzweifel/git-auto-commit-action@v5 - if: env.APPLY_FIXES_IF_COMMIT == 'true' - with: - branch: >- - ${{ - github.event.pull_request.head.ref || - github.head_ref || - github.ref - }} - commit_message: "[MegaLinter] Apply linters fixes" - commit_user_name: megalinter-bot - commit_user_email: megalinter@dotdot.horse diff --git a/.github/workflows/powershell.yml b/.github/workflows/powershell.yml index 836fab5..751e3ce 100644 --- a/.github/workflows/powershell.yml +++ b/.github/workflows/powershell.yml @@ -13,7 +13,6 @@ on: push: branches: [ "testing" ] pull_request: - branches: [ "testing" ] schedule: - cron: '41 14 * * 4' @@ -29,7 +28,7 @@ jobs: name: PSScriptAnalyzer runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Run PSScriptAnalyzer uses: microsoft/psscriptanalyzer-action@6b2948b1944407914a58661c49941824d149734f @@ -44,6 +43,6 @@ jobs: # Upload the SARIF file generated in the previous step - name: Upload SARIF results file - uses: github/codeql-action/upload-sarif@v2 + uses: github/codeql-action/upload-sarif@v3 with: sarif_file: results.sarif diff --git a/.gitignore b/.gitignore index cc65fe4..e4ece76 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,6 @@ -.DS_Store -.vs/* -.vscode/* -ADCSIssues.CSV -ADCSRemediation.CSV Artefacts/* Examples/Output/* Ignore/* -Invoke-RevertLocksmith.ps1 Lib/Core/* Lib/Default/* Lib/Standard/* @@ -19,3 +13,23 @@ Sources/*/bin Sources/*/*/obj Sources/*/*/bin Sources/packages/* + +### Visual Studio / Code ### +.vs/* +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +### Module Output Files ### +ADCSIssues.CSV +ADCSRemediation.CSV +Locksmith*ADCSIssues.CSV +Locksmith*ADCSRemediation.CSV +Invoke-RevertLocksmith.ps1 + +### OS Files ### +.DS_Store +Thumbs.db diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..c225e8f --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,25 @@ +# https://docs.readthedocs.io/en/stable/config-file/index.html + +# .readthedocs.yaml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +build: + os: ubuntu-22.04 + tools: + python: "3.12" + +mkdocs: + configuration: mkdocs.yml + +python: + install: + - requirements: docs/requirements.txt + +# # Build PDF & ePub +formats: all +# - epub +# - pdf diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 963ace4..9c2d2fc 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -4,5 +4,6 @@ "recommendations": [ "editorconfig.editorconfig", "ms-vscode.PowerShell", + "streetsidesoftware.code-spell-checker" ] } diff --git a/Build/MarkdownRepair.ps1 b/Build/MarkdownRepair.ps1 new file mode 100644 index 0000000..b31a95d --- /dev/null +++ b/Build/MarkdownRepair.ps1 @@ -0,0 +1,133 @@ +<# +.SYNOPSIS + Repair PlatyPS generated markdown files. +.NOTES + This file is temporarily required to handle platyPS help generation. + https://github.com/PowerShell/platyPS/issues/595 + This is a result of a breaking change introduced in PowerShell 7.4.0: + https://learn.microsoft.com/en-us/powershell/scripting/whats-new/what-s-new-in-powershell-74?view=powershell-7.4 + Breaking Changes: Added the ProgressAction parameter to the Common Parameters + modified from source: https://github.com/PowerShell/platyPS/issues/595#issuecomment-1820971702 +#> + +function Remove-CommonParameterFromMarkdown { + <# + .SYNOPSIS + Remove a PlatyPS generated parameter block. + .DESCRIPTION + Removes parameter block for the provided parameter name from the markdown file provided. + #> + param( + [Parameter(Mandatory)] + [string[]] + $Path, + + [Parameter(Mandatory = $false)] + [string[]] + $ParameterName = @('ProgressAction') + ) + $ErrorActionPreference = 'Stop' + foreach ($p in $Path) { + $content = (Get-Content -Path $p -Raw).TrimEnd() + $updateFile = $false + foreach ($param in $ParameterName) { + if (-not ($Param.StartsWith('-'))) { + $param = "-$($param)" + } + # Remove the parameter block + $pattern = "(?m)^### $param\r?\n[\S\s]*?(?=#{2,3}?)" + $newContent = $content -replace $pattern, '' + # Remove the parameter from the syntax block + $pattern = " \[$param\s?.*?]" + $newContent = $newContent -replace $pattern, '' + if ($null -ne (Compare-Object -ReferenceObject $content -DifferenceObject $newContent)) { + Write-Verbose "Added $param to $p" + # Update file content + $content = $newContent + $updateFile = $true + } + } + # Save file if content has changed + if ($updateFile) { + $newContent | Out-File -Encoding utf8 -FilePath $p + Write-Verbose "Updated file: $p" + } + } + return +} + +function Add-MissingCommonParameterToMarkdown { + param( + [Parameter(Mandatory)] + [string[]] + $Path, + + [Parameter(Mandatory = $false)] + [string[]] + $ParameterName = @('ProgressAction') + ) + $ErrorActionPreference = 'Stop' + foreach ($p in $Path) { + $content = (Get-Content -Path $p -Raw).TrimEnd() + $updateFile = $false + foreach ($NewParameter in $ParameterName) { + if (-not ($NewParameter.StartsWith('-'))) { + $NewParameter = "-$($NewParameter)" + } + $pattern = '(?m)^This cmdlet supports the common parameters:(.+?)\.' + $replacement = { + $Params = $_.Groups[1].Captures[0].ToString() -split ' ' + $CommonParameters = @() + foreach ($CommonParameter in $Params) { + if ($CommonParameter.StartsWith('-')) { + if ($CommonParameter.EndsWith(',')) { + $CleanParam = $CommonParameter.Substring(0, $CommonParameter.Length - 1) + } elseif ($p.EndsWith('.')) { + $CleanParam = $CommonParameter.Substring(0, $CommonParameter.Length - 1) + } else { + $CleanParam = $CommonParameter + } + $CommonParameters += $CleanParam + } + } + if ($NewParameter -notin $CommonParameters) { + $CommonParameters += $NewParameter + } + $CommonParameters[-1] = "and $($CommonParameters[-1]). " + return 'This cmdlet supports the common parameters: ' + (($CommonParameters | Sort-Object) -join ', ') + } + $newContent = $content -replace $pattern, $replacement + if ($null -ne (Compare-Object -ReferenceObject $content -DifferenceObject $newContent)) { + Write-Verbose "Added $NewParameter to $p" + $updateFile = $true + $content = $newContent + } + } + # Save file if content has changed + if ($updateFile) { + $newContent | Out-File -Encoding utf8 -FilePath $p + Write-Verbose "Updated file: $p" + } + } + return +} + +function Repair-PlatyPSMarkdown { + param( + [Parameter(Mandatory)] + [string[]] + $Path, + + [Parameter()] + [string[]] + $ParameterName = @('ProgressAction') + ) + $ErrorActionPreference = 'Stop' + $Parameters = @{ + Path = $Path + ParameterName = $ParameterName + } + $null = Remove-CommonParameterFromMarkdown @Parameters + $null = Add-MissingCommonParameterToMarkdown @Parameters + return +} diff --git a/Build/Write-HelpOutDocs.ps1 b/Build/Write-HelpOutDocs.ps1 new file mode 100644 index 0000000..5ac84c1 --- /dev/null +++ b/Build/Write-HelpOutDocs.ps1 @@ -0,0 +1,67 @@ +function Write-HelpOutDocs { + <# + .SYNOPSIS + Write module documentation using the HelpOut module. + + .DESCRIPTION + Writes module documentation using the HelpOut module. This functions generates the markdown and MAML help files from + comment-based help in each of the functions. It will also create the external help cab file. + + .EXAMPLE + Write-HelpOutDocs + + Does what it says on the tin. + + #> + [CmdletBinding()] + param () + + $ModuleName = 'Locksmith' + + # Remove the module from the current session to ensure we are working with the current source version. + Remove-Module -Name $ModuleName -Force -ErrorAction SilentlyContinue + + # Get the path to the module manifest. Check for either PSScriptRoot (if running from a script) or PWD (if running from the console). + $ModulePath = if ($PSScriptRoot) { + # If the $PSScriptRoot variable exists, check if you are in the build folder or the module folder. + if ( (Split-Path -Path $PSScriptRoot -Leaf) -eq 'Build' ) { + Split-Path -Path $PSScriptRoot -Parent + } elseif ( (Split-Path -Path $PSScriptRoot -Leaf) -match $ModuleName ) { + $PSScriptRoot + } else { + throw 'Failed to determine module manifest path. Please ensure you are in the module or build folder.' + } + } else { + # If the $PSScriptRoot variable does not exist, check if you are in the build folder or the module folder. + if ( (Split-Path -Path $PWD.Path -Leaf) -eq 'Build' ) { + Split-Path -Path $PWD -Parent + } elseif ( (Split-Path -Path $pwd -Leaf) -match $ModuleName ) { + $PWD + } else { + throw 'Failed to determine module manifest path. Please ensure you are in the module or build folder.' + } + } + + # All of the above is fun, but is largely not needed if you just run the script file instead of pasting the code into the console. + $ModuleManifestPath = Join-Path -Path $ModulePath -ChildPath "${ModuleName}.psd1" + + try { + Import-Module ServerManager -ErrorAction SilentlyContinue -WarningAction SilentlyContinue | Out-Null + Import-Module $ModuleManifestPath + } catch { + throw "Failed to import module manifest at $ModuleManifestPath. $_" + } + + Save-MarkdownHelp -Module Locksmith -ExcludeFile @('CODE_OF_CONDUCT.md', 'CONTRIBUTING.md', 'TSS Specs.md') + Save-MAML -Module Locksmith + + $params = @{ + CabFilesFolder = "$PSScriptRoot\..\en-US" + LandingPagePath = "$PSScriptRoot\..\docs\README.md" + OutputFolder = "$PSScriptRoot\..\en-US" + } + New-ExternalHelpCab @params + +} + +Write-HelpOutDocs diff --git a/Build/Write-PlatyPSDocs.ps1 b/Build/Write-PlatyPSDocs.ps1 new file mode 100644 index 0000000..7f8a0e3 --- /dev/null +++ b/Build/Write-PlatyPSDocs.ps1 @@ -0,0 +1,149 @@ +function Write-PlatyPSDocs { + <# + .SYNOPSIS + Use PlatyPS to create documentation for the current module. + + .DESCRIPTION + This function uses PlatyPS to create documentation for the current module. It will generate markdown files for each + function in the module, as well as a generic module page. Borrowing code from the Catesta module, by Jake Morrison + (@techthoughts2), this script also checks for issues and missing information in the help documentation before creating + an XML help file for online use. + + .EXAMPLE + Write-PlatyPSDocs + + Does what it says on the tin. + - Create markdown docs from comment-based help in each exported function. + - Create a general markdown page for the module. + - Create XML-based external help files for the module. + + #> + [CmdletBinding()] + param () + + $ModuleName = 'Locksmith' + $ModulePath = Split-Path -Path $PSScriptRoot -Parent + + # Remove the module from the current session to ensure we are working with the current source version. + Remove-Module -Name $ModuleName -Force -ErrorAction SilentlyContinue + + # Get the path to the module manifest. + $ModuleManifestPath = Join-Path -Path $PSScriptRoot -ChildPath "..\${ModuleName}.psd1" + + try { + Import-Module ServerManager -Force -ErrorAction SilentlyContinue -WarningAction SilentlyContinue | Out-Null + Import-Module $ModuleManifestPath -Force + $ModuleInfo = Test-ModuleManifest -Path $ModuleManifestPath + } catch { + throw "Failed to import module manifest at $ModuleManifestPath. $_" + } + + # Get module details from manifest + $ModuleVersion = $ModuleInfo.Version + $ModuleDescription = $ModuleInfo.Description + $FunctionsToExport = $ModuleInfo.ExportedFunctions + + # Prepare parameters for New-MarkdownHelp + $DocsFolder = Join-Path -Path $ModulePath -ChildPath 'Docs' + $ModulePage = Join-Path -Path $DocsFolder -ChildPath "$($ModuleName).md" + $markdownParams = @{ + Module = $ModuleName + OutputFolder = $DocsFolder + Force = $true + WithModulePage = $true + ModulePagePath = $ModulePage + Locale = 'en-US' + FwLink = 'NA' + HelpVersion = $ModuleVersion + Encoding = [System.Text.Encoding]::UTF8 + } + + # Generate markdown help files + New-MarkdownHelp @markdownParams | Out-Null + + + # Fixes and cleanup for markdown files # + + + # Fix formatting in multiline examples + Get-ChildItem -Path $DocsFolder -Recurse -File | ForEach-Object { + $Content = Get-Content $_.FullName -Raw + $NewContent = $Content -replace '(## EXAMPLE [^`]+?```\r\n[^`\r\n]+?\r\n)(```\r\n\r\n)([^#]+?\r\n)(\r\n)([^#]+)(#)', '$1$3$2$4$5$6' + if ($NewContent -ne $content) { + Set-Content -Path $_.FullName -Value $NewContent -Force + } + } + + # Repair markdown files generated by PlatyPS in PowerShell 7.4 + . $ModulePath\Build\MarkdownRepair.ps1 + $DocsFolder | Get-ChildItem -File | ForEach-Object { + Repair-PlatyPSMarkdown -Path $_.FullName + } + + # Replace each missing element we need for a proper generic module page .md file + $ModulePageFileContent = Get-Content -Raw $ModulePage + $ModulePageFileContent = $ModulePageFileContent -replace '{{ Fill in the Description }}', $ModuleDescription + $ModulePageFileContent | Out-File $ModulePage -Force -Encoding:utf8 + + # Replace each missing element we need for a proper function .md file + $FunctionsToExport | ForEach-Object { + $TextToReplace = "{{ Manually Enter $($_) Description Here }}" + $ReplacementText = (Get-Help -Detailed $_).Synopsis + $ModulePageFileContent = $ModulePageFileContent -replace $TextToReplace, $ReplacementText + } + $ModulePageFileContent | Out-File $ModulePage -Force -Encoding:utf8 + + # Check for missing or invalid GUID + $MissingGUID = Select-String -Path "$DocsFolder\*.md" -Pattern '(00000000-0000-0000-0000-000000000000)' + if ($MissingGUID.Count -gt 0) { + Write-Host 'The documentation that got generated resulted in a generic GUID. Check the GUID entry of your module manifest.' -ForegroundColor Yellow + throw 'Missing GUID. Please review and rebuild.' + } + + # Check for missing sections in markdown files + Write-Host 'Checking for missing documentation in MD files...' -ForegroundColor Gray + $MissingDocumentation = Select-String -Path "$DocsFolder\*.md" -Pattern '({{.*}})' + if ($MissingDocumentation.Count -gt 0) { + Write-Host 'The documentation that got generated resulted in missing sections which should be filled out. Please review the following sections in your comment based help, fill out missing information and rerun this build.' -ForegroundColor Yellow + Write-Host "(Note: This can happen if the .EXTERNALHELP CBH is defined for a function before running this build.)`n" -ForegroundColor Yellow + Write-Host "Path of files with issues: $DocsFolder\" -ForegroundColor Yellow + $MissingDocumentation | Select-Object FileName, LineNumber, Line | Format-Table -AutoSize + Write-Warning -Message 'Missing documentation. Please review and rebuild.' + } + + Write-Host 'Checking for missing SYNOPSIS in MD files...' -ForegroundColor Gray + $fSynopsisOutput = @() + $synopsisEval = Select-String -Path "$DocsFolder\*.md" -Pattern '^## SYNOPSIS$' -Context 0, 1 + $synopsisEval | ForEach-Object { + $chAC = $_.Context.DisplayPostContext.ToCharArray() + if ($null -eq $chAC) { + $fSynopsisOutput += $_.FileName + } + } + if ($fSynopsisOutput) { + Write-Host 'The following files are missing SYNOPSIS:' -ForegroundColor Yellow + $fSynopsisOutput + throw 'SYNOPSIS information missing. Please review.' + } + + + Write-Host 'Markdown generation complete.' -ForegroundColor Gray + + # Build the external xml help file from markdown help files with PlatyPS + Write-Host 'Creating external xml help file...' -ForegroundColor Gray + $null = New-ExternalHelp "$DocsFolder" -OutputPath "$PSScriptRoot\..\en-US\" -Force -Encoding ([System.Text.Encoding]::UTF8) + Write-Host '...External xml help file created!' -ForegroundColor Gray + + + # Create a CAB file for the external help + $params = @{ + CabFilesFolder = Join-Path -Path $PSScriptRoot -ChildPath '..\en-US' + LandingPagePath = Join-Path -Path $PSScriptRoot -ChildPath '..\Docs\Locksmith.md' + OutputFolder = Join-Path -Path $PSScriptRoot -ChildPath '..\en-US' + } + New-ExternalHelpCab @params + + +} # end function Write-PlatyPSDocs + +Write-PlatyPSDocs diff --git a/CBHELP.md b/CBHELP.md deleted file mode 100644 index 037ce61..0000000 --- a/CBHELP.md +++ /dev/null @@ -1,59 +0,0 @@ -## Comment-based Help pulled from original version -```` powershell -<# -.PARAMETER Forest -Specifies a single forest to be scanned by Invoke-Locksmith.ps1. Useful in large environments that may -take a while to enumerate. - -.PARAMETER InputPath -Specifies an input file containing a list of forests to be checked. Input file should consist of -a forest per line of the input file. If this parameter is not defined at runtime, -Invoke-Locksmith.ps1 will attempt to scan every AD CS installation it can find in the forest. - -.PARAMETER OutputPath -Specifies the name and path for the CSV-based output file. If this parameter is not defined at runtime, -Invoke-Locksmith.ps1 will output its results to the console. - -.EXAMPLE -PS> .\Invoke-Locksmith.ps1 - -Description -------------------------- -Running Invoke-Locksmith.ps1 with no parameters configured will scan any AD CS installation accessible to the user -and output all discovered AD CS issues to the console. - -.EXAMPLE -PS> .\Invoke-Locksmith.ps1 -InputPath .\TrustedForests.txt - -Description -------------------------- -Specifying an input file of forests will force Invoke-Locksmith.ps1 to attempt to scan the specific forests -listed in TrustedForests.txt regardless of permissions or visibility into the forest. Because no Mode is -defined, identified issues will not be written to files. - -.EXAMPLE -PS> .\Invoke-Locksmith.ps1 -Mode 2 -OutputPath C:\Users\thanks\Documents - -Description -------------------------- -In Mode 1, Locksmith will scan all AD CS installations it can find and write its findings to a series -of CSVs in C:\Users\thanks\Documents. - -.EXAMPLE -PS> .\Invoke-Locksmith.ps1 -Mode 3 -Forest it.example.com - -Description -------------------------- -In this example, Locksmith will only scan the AD CS installation of it.example.com, regardless of how many -forests it can actually access. All malconfigurations and snippets to fix them will output to the local path. - -.EXAMPLE -PS> .\Invoke-Locksmith.ps1 -Mode 4 -OutputPath E:\ADisCheeseSwiss - -Description -------------------------- -Mode 3 is the "easy button." Running Locksmith in Mode 3 will identify all malconfigs and output them to CSV -files located in E:\ADisCheeseSwiss. Then it will display the snippets it plans to run and waits for human -interaction to confirm everything looks correct. -#> -```` diff --git a/Docs/Invoke-Locksmith.md b/Docs/Invoke-Locksmith.md new file mode 100644 index 0000000..b08af08 --- /dev/null +++ b/Docs/Invoke-Locksmith.md @@ -0,0 +1,149 @@ +--- +external help file: Locksmith-help.xml +Module Name: Locksmith +online version: +schema: 2.0.0 +--- + +# Invoke-Locksmith + +## SYNOPSIS +Finds the most common malconfigurations of Active Directory Certificate Services (AD CS). + +## SYNTAX + +``` +Invoke-Locksmith [[-Mode] ] [[-Scans] ] [[-OutputPath] ] [[-Credential] ] + [] +``` + +## DESCRIPTION +Locksmith uses the Active Directory (AD) Powershell (PS) module to identify 10 misconfigurations +commonly found in Enterprise mode AD CS installations. + +## EXAMPLES + +### EXAMPLE 1 +``` +Invoke-Locksmith -Mode 0 -Scans All -OutputPath 'C:\Temp' +``` + +Finds all malconfigurations and displays them in the console. + +### EXAMPLE 2 +``` +Invoke-Locksmith -Mode 2 -Scans All -OutputPath 'C:\Temp' +``` + +Finds all malconfigurations and displays them in the console. +The findings are saved in a CSV file in C:\Temp. + +## PARAMETERS + +### -Mode +Specifies sets of common script execution modes. + +-Mode 0 +Finds any malconfigurations and displays them in the console. +No attempt is made to fix identified issues. + +-Mode 1 +Finds any malconfigurations and displays them in the console. +Displays example Powershell snippet that can be used to resolve the issue. +No attempt is made to fix identified issues. + +-Mode 2 +Finds any malconfigurations and writes them to a series of CSV files. +No attempt is made to fix identified issues. + +-Mode 3 +Finds any malconfigurations and writes them to a series of CSV files. +Creates code snippets to fix each issue and writes them to an environment-specific custom .PS1 file. +No attempt is made to fix identified issues. + +-Mode 4 +Finds any malconfigurations and creates code snippets to fix each issue. +Attempts to fix all identified issues. +This mode may require high-privileged access. + +```yaml +Type: Int32 +Parameter Sets: (All) +Aliases: + +Required: False +Position: 1 +Default value: 0 +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Scans +Specify which scans you want to run. +Available scans: 'All' or Auditing, ESC1, ESC2, ESC3, ESC4, ESC5, ESC6, ESC8, or 'PromptMe' + +-Scans All +Run all scans (default). + +-Scans PromptMe +Presents a grid view of the available scan types that can be selected and run them after you click OK. + +```yaml +Type: Array +Parameter Sets: (All) +Aliases: + +Required: False +Position: 2 +Default value: All +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -OutputPath +Specify the path where you want to save reports and mitigation scripts. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: False +Position: 3 +Default value: $PWD +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Credential +The credential to use for working with ADCS. + +```yaml +Type: PSCredential +Parameter Sets: (All) +Aliases: + +Required: False +Position: 4 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutBuffer, -OutVariable, -PipelineVariable, -Verbose, -WarningAction, -WarningVariable, and -ProgressAction. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +### None. You cannot pipe objects to Invoke-Locksmith.ps1. +## OUTPUTS + +### Output types: +### 1. Console display of identified issues. +### 2. Console display of identified issues and their fixes. +### 3. CSV containing all identified issues. +### 4. CSV containing all identified issues and their fixes. +## NOTES +The Windows PowerShell cmdlet Restart-Service requires RunAsAdministrator. + +## RELATED LINKS diff --git a/Docs/Locksmith.md b/Docs/Locksmith.md new file mode 100644 index 0000000..ce80ef9 --- /dev/null +++ b/Docs/Locksmith.md @@ -0,0 +1,15 @@ +--- +Module Name: Locksmith +Module Guid: b1325b42-8dc4-4f17-aa1f-dcb5984ca14a +Download Help Link: NA +Help Version: 2024.11.11 +Locale: en-US +--- + +# Locksmith Module +## Description +A small tool to find and fix common misconfigurations in Active Directory Certificate Services. + +## Locksmith Cmdlets +### [Invoke-Locksmith](Invoke-Locksmith.md) +A small tool to find and fix common misconfigurations in Active Directory Certificate Services. diff --git a/Docs/img/favicon.ico b/Docs/img/favicon.ico new file mode 100644 index 0000000..c8eafb4 Binary files /dev/null and b/Docs/img/favicon.ico differ diff --git a/Docs/img/locksmith-sticker.png b/Docs/img/locksmith-sticker.png new file mode 100644 index 0000000..9cba729 Binary files /dev/null and b/Docs/img/locksmith-sticker.png differ diff --git a/Docs/img/locksmith.ico b/Docs/img/locksmith.ico new file mode 100644 index 0000000..c8eafb4 Binary files /dev/null and b/Docs/img/locksmith.ico differ diff --git a/Docs/requirements.txt b/Docs/requirements.txt new file mode 100644 index 0000000..10179c9 --- /dev/null +++ b/Docs/requirements.txt @@ -0,0 +1,6 @@ +# https://github.com/readthedocs-examples/example-mkdocs-basic/blob/main/docs/requirements.txt +# requirements.txt +jinja2==3.1.4 #https://pypi.org/project/Jinja2/ +mkdocs>=1.6.0 #https://github.com/mkdocs/mkdocs +mkdocs-material==9.5.25 #https://github.com/squidfunk/mkdocs-material +pygments>=2.18.0 #https://pypi.org/project/Pygments/ diff --git a/Flowcharts/Auditing.png b/Images/Flowcharts/Auditing.png similarity index 100% rename from Flowcharts/Auditing.png rename to Images/Flowcharts/Auditing.png diff --git a/Flowcharts/ESC1.png b/Images/Flowcharts/ESC1.png similarity index 100% rename from Flowcharts/ESC1.png rename to Images/Flowcharts/ESC1.png diff --git a/Flowcharts/ESC2.png b/Images/Flowcharts/ESC2.png similarity index 100% rename from Flowcharts/ESC2.png rename to Images/Flowcharts/ESC2.png diff --git a/Flowcharts/ESC3.png b/Images/Flowcharts/ESC3.png similarity index 100% rename from Flowcharts/ESC3.png rename to Images/Flowcharts/ESC3.png diff --git a/Flowcharts/ESC4.png b/Images/Flowcharts/ESC4.png similarity index 100% rename from Flowcharts/ESC4.png rename to Images/Flowcharts/ESC4.png diff --git a/Flowcharts/ESC5.png b/Images/Flowcharts/ESC5.png similarity index 100% rename from Flowcharts/ESC5.png rename to Images/Flowcharts/ESC5.png diff --git a/Flowcharts/ESC6.png b/Images/Flowcharts/ESC6.png similarity index 100% rename from Flowcharts/ESC6.png rename to Images/Flowcharts/ESC6.png diff --git a/Flowcharts/ESC8.png b/Images/Flowcharts/ESC8.png similarity index 100% rename from Flowcharts/ESC8.png rename to Images/Flowcharts/ESC8.png diff --git a/Locksmith.psd1 b/Locksmith.psd1 index 819bc22..3e1b4f2 100644 --- a/Locksmith.psd1 +++ b/Locksmith.psd1 @@ -5,7 +5,7 @@ CompatiblePSEditions = @('Desktop', 'Core') Copyright = '(c) 2022 - 2024. All rights reserved.' Description = 'A small tool to find and fix common misconfigurations in Active Directory Certificate Services.' - FunctionsToExport = @('*') + FunctionsToExport = 'Invoke-Locksmith' GUID = 'b1325b42-8dc4-4f17-aa1f-dcb5984ca14a' ModuleVersion = '2024.12.13' PowerShellVersion = '5.1' @@ -19,4 +19,5 @@ } RequiredModules = @('ActiveDirectory', 'ServerManager', 'Microsoft.PowerShell.Utility', 'Microsoft.PowerShell.LocalAccounts', 'Microsoft.PowerShell.Management', 'Microsoft.PowerShell.Security', 'CimCmdlets', 'Dism') RootModule = 'Locksmith.psm1' -} \ No newline at end of file + HelpInfoURI = 'https://raw.githubusercontent.com/TrimarcJake/Locksmith/main/en-US/' +} diff --git a/Private/Export-RevertScript.ps1 b/Private/Export-RevertScript.ps1 index 8632239..b368dba 100644 --- a/Private/Export-RevertScript.ps1 +++ b/Private/Export-RevertScript.ps1 @@ -5,7 +5,7 @@ .DESCRIPTION This script is used to revert changes performed by Locksmith. - It takes in various arrays of objects representing auditing issues and ESC misconfirugrations. + It takes in various arrays of objects representing auditing issues and ESC misconfigurations. It creates a new script called 'Invoke-RevertLocksmith.ps1' and adds the necessary commands to revert the changes made by Locksmith. diff --git a/Private/Find-ESC11.ps1 b/Private/Find-ESC11.ps1 index 851dd93..b105423 100644 --- a/Private/Find-ESC11.ps1 +++ b/Private/Find-ESC11.ps1 @@ -41,7 +41,7 @@ Revert = 'N/A' } if ($_.InterfaceFlag -eq 'No') { - $Issue.Issue = @" + $Issue.Issue = @' The IF_ENFORCEENCRYPTICERTREQUEST flag is disabled on this Certification Authority (CA). It is possible to relay NTLM authentication to the RPC interface of this CA. @@ -53,12 +53,12 @@ receive a certificate which can be used to authenticate as that DC. More info: - https://blog.compass-security.com/2022/11/relaying-to-ad-certificate-services-over-rpc/ -"@ +'@ $Issue.Fix = @" # Enable the flag certutil -config $CAFullname -setreg CA\InterfaceFlags +IF_ENFORCEENCRYPTICERTREQUEST -# Restart the Ceritification Authority service +# Restart the Certificate Authority service Invoke-Command -ComputerName `'$($_.dNSHostName)`' -ScriptBlock { Get-Service -Name `'certsvc`' | Restart-Service -Force } @@ -67,7 +67,7 @@ Invoke-Command -ComputerName `'$($_.dNSHostName)`' -ScriptBlock { # Disable the flag certutil -config $CAFullname -setreg CA\InterfaceFlags -IF_ENFORCEENCRYPTICERTREQUEST -# Restart the Ceritification Authority service +# Restart the Certificate Authority service Invoke-Command -ComputerName `'$($_.dNSHostName)`' -ScriptBlock { Get-Service -Name `'certsvc`' | Restart-Service -Force } diff --git a/Private/Find-ESC4.ps1 b/Private/Find-ESC4.ps1 index 4ad07c9..02b6530 100644 --- a/Private/Find-ESC4.ps1 +++ b/Private/Find-ESC4.ps1 @@ -21,7 +21,7 @@ Specifies the list of SIDs of safe users who are allowed to have specific rights on the objects. This parameter is mandatory. .PARAMETER SafeObjectTypes - Specifices a list of ObjectTypes which are not a security concern. This parameter is mandatory. + Specifies a list of ObjectTypes which are not a security concern. This parameter is mandatory. .OUTPUTS The script outputs an array of custom objects representing the matching ADCS objects and their associated information. diff --git a/Private/Find-ESC5.ps1 b/Private/Find-ESC5.ps1 index 4c09f23..8e78e96 100644 --- a/Private/Find-ESC5.ps1 +++ b/Private/Find-ESC5.ps1 @@ -21,7 +21,7 @@ Specifies the list of SIDs of safe users who are allowed to have specific rights on the objects. This parameter is mandatory. .PARAMETER SafeObjectTypes - Specifices a list of ObjectTypes which are not a security concern. This parameter is mandatory. + Specifies a list of ObjectTypes that are not a security concern. This parameter is mandatory. .OUTPUTS The script outputs an array of custom objects representing the matching ADCS objects and their associated information. diff --git a/Private/Find-ESC6.ps1 b/Private/Find-ESC6.ps1 index b544b02..9c2ac0e 100644 --- a/Private/Find-ESC6.ps1 +++ b/Private/Find-ESC6.ps1 @@ -61,7 +61,7 @@ More info: # Disable the flag certutil -config $CAFullname -setreg policy\EditFlags -EDITF_ATTRIBUTESUBJECTALTNAME2 -# Restart the Ceritification Authority service +# Restart the Certificate Authority service Invoke-Command -ComputerName `'$($_.dNSHostName)`' -ScriptBlock { Get-Service -Name `'certsvc`' | Restart-Service -Force } @@ -70,7 +70,7 @@ Invoke-Command -ComputerName `'$($_.dNSHostName)`' -ScriptBlock { # Enable the flag certutil -config $CAFullname -setreg policy\EditFlags +EDITF_ATTRIBUTESUBJECTALTNAME2 -# Restart the Ceritification Authority service +# Restart the Certificate Authority service Invoke-Command -ComputerName `'$($_.dNSHostName)`' -ScriptBlock { Get-Service -Name `'certsvc`' | Restart-Service -Force } diff --git a/Private/Find-ESC8.ps1 b/Private/Find-ESC8.ps1 index 7d402b6..13464a4 100644 --- a/Private/Find-ESC8.ps1 +++ b/Private/Find-ESC8.ps1 @@ -50,12 +50,12 @@ authentication to this HTTP endpoint. If the LAN Manager authentication level of any domain in this forest is 2 or less, an attacker can coerce authentication from a Domain Controller (DC) and -relay it to this HTTP enrollment enpoint to receive a certificate which can be +relay it to this HTTP enrollment endpoint to receive a certificate which can be used to authenticate as that DC. More info: - https://posts.specterops.io/certified-pre-owned-d95910965cd2 - + '@ Fix = @' Disable HTTP access and enforce HTTPS. @@ -74,7 +74,7 @@ possible. If those protection are not in place, and the LAN Manager authentication level of any domain in this forest is 2 or less, an attacker can coerce authentication -from a Domain Controller (DC) and relay it to this HTTPS enrollment enpoint to +from a Domain Controller (DC) and relay it to this HTTPS enrollment endpoint to receive a certificate which can be used to authenticate as that DC. '@ diff --git a/Private/Find-ESC9.ps1 b/Private/Find-ESC9.ps1 index ee4f992..c271c1e 100644 --- a/Private/Find-ESC9.ps1 +++ b/Private/Find-ESC9.ps1 @@ -4,12 +4,12 @@ #> function Find-ESC9 { -<# + <# .SYNOPSIS Checks for ESC9 (No Security Extension) Vulnerability .DESCRIPTION - This function checks for certificate templates that contain the flag CT_CLAG_NO_SECURITY_EXTENSION (0x80000), + This function checks for certificate templates that contain the flag CT_FLAG_NO_SECURITY_EXTENSION (0x80000), which will likely make them vulnerable to ESC9. Another factor to check for ESC9 is the registry values on AD domain controllers that can help harden certificate based authentication for Kerberos and SChannel. @@ -17,7 +17,7 @@ function Find-ESC9 { An ESC9 condition exists when: - the new msPKI-Enrollment-Flag value on a certificate contains the flag CT_FLAG_NO_SECURITY_EXTENSION (0x80000) - - AND an insecure regstry value is set on domain controllers: + - AND an insecure registry value is set on domain controllers: - the StrongCertificateBindingEnforcement registry value for Kerberos is not set to 2 (the default is 1) on domain controllers at HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Kdc @@ -81,31 +81,30 @@ function Find-ESC9 { Import-Module ActiveDirectory - $templates = Get-ADObject -Filter {ObjectClass -eq "pKICertificateTemplate"} -Properties * + $templates = Get-ADObject -Filter { ObjectClass -eq 'pKICertificateTemplate' } -Properties * foreach ($template in $templates) { $name = $template.Name - $subjectNameFlag = $template.'msPKI-Cert-Template-OID' - $subjectType = $template.'msPKI-Certificate-Application-Policy' - $enrollmentFlag = $template.'msPKI-Enrollment-Flag' + $subjectNameFlag = $template.'msPKI-Cert-Template-OID' + $subjectType = $template.'msPKI-Certificate-Application-Policy' + $enrollmentFlag = $template.'msPKI-Enrollment-Flag' $certificateNameFlag = $template.'msPKI-Certificate-Name-Flag' # Check if the template is vulnerable to ESC9 - if ($subjectNameFlag -eq "Supply in the request" -and - ($subjectType -eq "User" -or $subjectType -eq "Computer") -and - # 0x200 means a certificate needs to include a template name certificate extension - # 0x220 instructs the client to perform autoenrollment for the specified template + if ($subjectNameFlag -eq 'Supply in the request' -and + ($subjectType -eq 'User' -or $subjectType -eq 'Computer') -and + # 0x200 means a certificate needs to include a template name certificate extension + # 0x220 instructs the client to perform auto-enrollment for the specified template ($enrollmentFlag -eq 0x200 -or $enrollmentFlag -eq 0x220) -and - # 0x2 instructs the client to supply subject information in the certificate request (CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT). - # This means that any user who is allowed to enroll in a certificate with this setting can request a certificate as any - # user in the network, including a privileged user. - # 0x3 instructs the client to supply both the subject and subject alternate name information in the certificate request + # 0x2 instructs the client to supply subject information in the certificate request (CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT). + # This means that any user who is allowed to enroll in a certificate with this setting can request a certificate as any + # user in the network, including a privileged user. + # 0x3 instructs the client to supply both the subject and subject alternate name information in the certificate request ($certificateNameFlag -eq 0x2 -or $certificateNameFlag -eq 0x3)) { # Print the template name and the vulnerability Write-Output "$name is vulnerable to ESC9" - } - else { + } else { # Print the template name and the status Write-Output "$name is not vulnerable to ESC9" } diff --git a/Private/Invoke-Remediation.ps1 b/Private/Invoke-Remediation.ps1 index 5e7b301..3cd5ec6 100644 --- a/Private/Invoke-Remediation.ps1 +++ b/Private/Invoke-Remediation.ps1 @@ -72,9 +72,9 @@ function Invoke-Remediation { Export-RevertScript @params } catch { Write-Warning 'Creation of Invoke-RevertLocksmith.ps1 failed.' - Write-Host "Continue with this operation? [Y] Yes " -NoNewline - Write-Host "[N] " -ForegroundColor Yellow -NoNewline - Write-Host "No: " -NoNewLine + Write-Host 'Continue with this operation? [Y] Yes ' -NoNewline + Write-Host '[N] ' -ForegroundColor Yellow -NoNewline + Write-Host 'No: ' -NoNewline $WarningError = '' $WarningError = Read-Host if ($WarningError -like 'y') { @@ -98,9 +98,9 @@ function Invoke-Remediation { Write-Host 'OPERATIONAL IMPACT:' -ForegroundColor White Write-Host "This change should have little to no impact on the AD CS environment.`n" -ForegroundColor Green Write-Host "If you continue, Locksmith will attempt to fix this issue.`n" -ForegroundColor Yellow - Write-Host "Continue with this operation? [Y] Yes " -NoNewline - Write-Host "[N] " -ForegroundColor Yellow -NoNewline - Write-Host "No: " -NoNewLine + Write-Host 'Continue with this operation? [Y] Yes ' -NoNewline + Write-Host '[N] ' -ForegroundColor Yellow -NoNewline + Write-Host 'No: ' -NoNewline $WarningError = '' $WarningError = Read-Host if ($WarningError -like 'y') { @@ -123,15 +123,15 @@ function Invoke-Remediation { Write-Host "$($_.Technique)`n" Write-Host 'ACTION TO BE PERFORMED:' -ForegroundColor White Write-Host "Locksmith will attempt to enable Manager Approval on the `"$($_.Name)`" template.`n" - Write-Host 'CCOMMAND(S) TO BE RUN:' + Write-Host 'COMMAND(S) TO BE RUN:' Write-Host 'PS> ' -NoNewline Write-Host "$($_.Fix)`n" -ForegroundColor Cyan Write-Host 'OPERATIONAL IMPACT:' -ForegroundColor White Write-Host "WARNING: This change could cause some services to stop working until certificates are approved.`n" -ForegroundColor Yellow Write-Host "If you continue, Locksmith will attempt to fix this issue.`n" -ForegroundColor Yellow - Write-Host "Continue with this operation? [Y] Yes " -NoNewline - Write-Host "[N] " -ForegroundColor Yellow -NoNewline - Write-Host "No: " -NoNewLine + Write-Host 'Continue with this operation? [Y] Yes ' -NoNewline + Write-Host '[N] ' -ForegroundColor Yellow -NoNewline + Write-Host 'No: ' -NoNewline $WarningError = '' $WarningError = Read-Host if ($WarningError -like 'y') { @@ -161,9 +161,9 @@ function Invoke-Remediation { Write-Host 'OPERATIONAL IMPACT:' -ForegroundColor White Write-Host "WARNING: This change could cause some services to stop working until certificates are approved.`n" -ForegroundColor Yellow Write-Host "If you continue, Locksmith will attempt to fix this issue.`n" -ForegroundColor Yellow - Write-Host "Continue with this operation? [Y] Yes " -NoNewline - Write-Host "[N] " -ForegroundColor Yellow -NoNewline - Write-Host "No: " -NoNewLine + Write-Host 'Continue with this operation? [Y] Yes ' -NoNewline + Write-Host '[N] ' -ForegroundColor Yellow -NoNewline + Write-Host 'No: ' -NoNewline $WarningError = '' $WarningError = Read-Host if ($WarningError -like 'y') { @@ -178,7 +178,7 @@ function Invoke-Remediation { } } if ($ESC4) { - $ESC4 | Where-Object Issue -like "* Owner rights *" | ForEach-Object { # This selector sucks - Jake + $ESC4 | Where-Object Issue -Like '* Owner rights *' | ForEach-Object { # This selector sucks - Jake $FixBlock = [scriptblock]::Create($_.Fix) Write-Host 'ISSUE:' -ForegroundColor White Write-Host "$($_.Issue)`n" @@ -192,9 +192,9 @@ function Invoke-Remediation { Write-Host 'OPERATIONAL IMPACT:' -ForegroundColor White Write-Host "This change should have little to no impact on the AD CS environment.`n" -ForegroundColor Green Write-Host "If you continue, Locksmith will attempt to fix this issue.`n" -ForegroundColor Yellow - Write-Host "Continue with this operation? [Y] Yes " -NoNewline - Write-Host "[N] " -ForegroundColor Yellow -NoNewline - Write-Host "No: " -NoNewLine + Write-Host 'Continue with this operation? [Y] Yes ' -NoNewline + Write-Host '[N] ' -ForegroundColor Yellow -NoNewline + Write-Host 'No: ' -NoNewline $WarningError = '' $WarningError = Read-Host if ($WarningError -like 'y') { @@ -209,7 +209,7 @@ function Invoke-Remediation { } } if ($ESC5) { - $ESC5 | Where-Object Issue -like "* Owner rights *" | ForEach-Object { # TODO This selector sucks - Jake + $ESC5 | Where-Object Issue -Like '* Owner rights *' | ForEach-Object { # TODO This selector sucks - Jake $FixBlock = [scriptblock]::Create($_.Fix) Write-Host 'ISSUE:' -ForegroundColor White Write-Host "$($_.Issue)`n" @@ -223,9 +223,9 @@ function Invoke-Remediation { Write-Host 'OPERATIONAL IMPACT:' -ForegroundColor White Write-Host "This change should have little to no impact on the AD CS environment.`n" -ForegroundColor Green Write-Host "If you continue, Locksmith will attempt to fix this issue.`n" -ForegroundColor Yellow - Write-Host "Continue with this operation? [Y] Yes " -NoNewline - Write-Host "[N] " -ForegroundColor Yellow -NoNewline - Write-Host "No: " -NoNewLine + Write-Host 'Continue with this operation? [Y] Yes ' -NoNewline + Write-Host '[N] ' -ForegroundColor Yellow -NoNewline + Write-Host 'No: ' -NoNewline $WarningError = '' $WarningError = Read-Host if ($WarningError -like 'y') { @@ -247,7 +247,7 @@ function Invoke-Remediation { Write-Host 'TECHNIQUE:' -ForegroundColor White Write-Host "$($_.Technique)`n" Write-Host 'ACTION TO BE PERFORMED:' -ForegroundColor White - Write-Host "Locksmith will attempt to disable the EDITF_ATTRIBUTESUBJECTALTNAME2 flag on Certifiction Authority `"$($_.Name)`".`n" + Write-Host "Locksmith will attempt to disable the EDITF_ATTRIBUTESUBJECTALTNAME2 flag on the Certificate Authority `"$($_.Name)`".`n" Write-Host 'COMMAND(S) TO BE RUN' -ForegroundColor White Write-Host 'PS> ' -NoNewline Write-Host "$($_.Fix)`n" -ForegroundColor Cyan @@ -255,9 +255,9 @@ function Invoke-Remediation { Write-Host 'OPERATIONAL IMPACT:' -ForegroundColor White Write-Host "WARNING: This change could cause some services to stop working.`n" -ForegroundColor Yellow Write-Host "If you continue, Locksmith will attempt to fix this issue.`n" -ForegroundColor Yellow - Write-Host "Continue with this operation? [Y] Yes " -NoNewline - Write-Host "[N] " -ForegroundColor Yellow -NoNewline - Write-Host "No: " -NoNewLine + Write-Host 'Continue with this operation? [Y] Yes ' -NoNewline + Write-Host '[N] ' -ForegroundColor Yellow -NoNewline + Write-Host 'No: ' -NoNewline $WarningError = '' $WarningError = Read-Host if ($WarningError -like 'y') { @@ -280,7 +280,7 @@ function Invoke-Remediation { Write-Host 'TECHNIQUE:' -ForegroundColor White Write-Host "$($_.Technique)`n" Write-Host 'ACTION TO BE PERFORMED:' -ForegroundColor White - Write-Host "Locksmith will attempt to enable the IF_ENFORCEENCRYPTICERTREQUEST flag on Certifiction Authority `"$($_.Name)`".`n" + Write-Host "Locksmith will attempt to enable the IF_ENFORCEENCRYPTICERTREQUEST flag on the Certificate Authority `"$($_.Name)`".`n" Write-Host 'COMMAND(S) TO BE RUN' -ForegroundColor White Write-Host 'PS> ' -NoNewline Write-Host "$($_.Fix)`n" -ForegroundColor Cyan @@ -288,9 +288,9 @@ function Invoke-Remediation { Write-Host 'OPERATIONAL IMPACT:' -ForegroundColor White Write-Host "WARNING: This change could cause some services to stop working.`n" -ForegroundColor Yellow Write-Host "If you continue, Locksmith will attempt to fix this issue.`n" -ForegroundColor Yellow - Write-Host "Continue with this operation? [Y] Yes " -NoNewline - Write-Host "[N] " -ForegroundColor Yellow -NoNewline - Write-Host "No: " -NoNewLine + Write-Host 'Continue with this operation? [Y] Yes ' -NoNewline + Write-Host '[N] ' -ForegroundColor Yellow -NoNewline + Write-Host 'No: ' -NoNewline $WarningError = '' $WarningError = Read-Host if ($WarningError -like 'y') { @@ -320,9 +320,9 @@ function Invoke-Remediation { Write-Host 'OPERATIONAL IMPACT:' -ForegroundColor White Write-Host "WARNING: This change could cause some services to stop working until certificates are approved.`n" -ForegroundColor Yellow Write-Host "If you continue, Locksmith will attempt to fix this issue.`n" -ForegroundColor Yellow - Write-Host "Continue with this operation? [Y] Yes " -NoNewline - Write-Host "[N] " -ForegroundColor Yellow -NoNewline - Write-Host "No: " -NoNewLine + Write-Host 'Continue with this operation? [Y] Yes ' -NoNewline + Write-Host '[N] ' -ForegroundColor Yellow -NoNewline + Write-Host 'No: ' -NoNewline $WarningError = '' $WarningError = Read-Host if ($WarningError -like 'y') { diff --git a/Private/New-Dictionary.ps1 b/Private/New-Dictionary.ps1 index 75b430c..799f1f8 100644 --- a/Private/New-Dictionary.ps1 +++ b/Private/New-Dictionary.ps1 @@ -14,8 +14,8 @@ descriptions, code used to find, code used to fix, and reference URLs. This is i Category The high level category of VCI types, including escalation path, server configuration, GPO setting, etc. Subcategory The subcategory of vulnerable configuration item types. Summary A summary of the vulnerability and how it can be abused. - FindIt The name of the function that is used to look for the VCI, stored as an invokable scriptblock. - FixIt The name of the function that is used to fix the VCI, stored as an invokable scriptblock. + FindIt The name of the function that is used to look for the VCI, stored as an invocable scriptblock. + FixIt The name of the function that is used to fix the VCI, stored as an invocable scriptblock. ReferenceUrls An array of URLs that are used as references to learn more about the VCI. #> @@ -23,7 +23,7 @@ function New-Dictionary { class VulnerableConfigurationItem { static [string] $Version = '2024.11.03.000' [string]$Name - [ValidateSet('Escalation Path','Server Configuration','GPO Setting')][string]$Category + [ValidateSet('Escalation Path', 'Server Configuration', 'GPO Setting')][string]$Category [string]$Subcategory [string]$Summary [scriptblock]$FindIt @@ -33,78 +33,78 @@ function New-Dictionary { [VulnerableConfigurationItem[]]$Dictionary = @( [VulnerableConfigurationItem]@{ - Name = 'ESC1' - Category = 'Escalation Path' - Subcategory = 'Vulnerable Client Authentication Templates' - Summary = '' - FindIt = {Find-ESC1} - FixIt = {Write-Output "Add code to fix the vulnerable configuration."} + Name = 'ESC1' + Category = 'Escalation Path' + Subcategory = 'Vulnerable Client Authentication Templates' + Summary = '' + FindIt = { Find-ESC1 } + FixIt = { Write-Output 'Add code to fix the vulnerable configuration.' } ReferenceUrls = 'https://posts.specterops.io/certified-pre-owned-d95910965cd2#:~:text=Misconfigured%20Certificate%20Templates%20%E2%80%94%20ESC1' }, [VulnerableConfigurationItem]@{ - Name = 'ESC2' - Category = 'Escalation Path' - Subcategory = 'Vulnerable SubCA/Any Purpose Templates' - Summary = '' - FindIt = {Find-ESC2} - FixIt = {Write-Output 'Add code to fix the vulnerable configuration.'} + Name = 'ESC2' + Category = 'Escalation Path' + Subcategory = 'Vulnerable SubCA/Any Purpose Templates' + Summary = '' + FindIt = { Find-ESC2 } + FixIt = { Write-Output 'Add code to fix the vulnerable configuration.' } ReferenceUrls = 'https://posts.specterops.io/certified-pre-owned-d95910965cd2#:~:text=Misconfigured%20Certificate%20Templates%20%E2%80%94%20ESC2' }, [VulnerableConfigurationItem]@{ - Name = 'ESC3' - Category = 'Escalation Path' - Subcategory = 'Vulnerable Enrollment Agent Templates' - Summary = '' - FindIt = { + Name = 'ESC3' + Category = 'Escalation Path' + Subcategory = 'Vulnerable Enrollment Agent Templates' + Summary = '' + FindIt = { Find-ESC3Condition1 Find-ESC3Condition2 } - FixIt = {Write-Output 'Add code to fix the vulnerable configuration.'} + FixIt = { Write-Output 'Add code to fix the vulnerable configuration.' } ReferenceUrls = 'https://posts.specterops.io/certified-pre-owned-d95910965cd2#:~:text=Enrollment%20Agent%20Templates%20%E2%80%94%20ESC3' }, [VulnerableConfigurationItem]@{ - Name = 'ESC4'; - Category = 'Escalation Path' - Subcategory = 'Certificate Templates with Vulnerable Access Controls' - Summary = '' - FindIt = {Find-ESC4} - FixIt = {Write-Output 'Add code to fix the vulnerable configuration.'} + Name = 'ESC4' + Category = 'Escalation Path' + Subcategory = 'Certificate Templates with Vulnerable Access Controls' + Summary = '' + FindIt = { Find-ESC4 } + FixIt = { Write-Output 'Add code to fix the vulnerable configuration.' } ReferenceUrls = 'https://posts.specterops.io/certified-pre-owned-d95910965cd2#:~:text=Vulnerable%20Certificate%20Template%20Access%20Control%20%E2%80%94%20ESC4' }, [VulnerableConfigurationItem]@{ - Name = 'ESC5'; - Category = 'Escalation Path' - Subcategory = 'PKI Objects with Vulnerable Access Control' - Summary = '' - FindIt = {Find-ESC5} - FixIt = {Write-Output 'Add code to fix the vulnerable configuration.'} + Name = 'ESC5' + Category = 'Escalation Path' + Subcategory = 'PKI Objects with Vulnerable Access Control' + Summary = '' + FindIt = { Find-ESC5 } + FixIt = { Write-Output 'Add code to fix the vulnerable configuration.' } ReferenceUrls = 'https://posts.specterops.io/certified-pre-owned-d95910965cd2#:~:text=Vulnerable%20PKI%20Object%20Access%20Control%20%E2%80%94%20ESC5' }, [VulnerableConfigurationItem]@{ - Name = 'ESC6' - Category = 'Escalation Path' - Subcategory = 'EDITF_ATTRIBUTESUBJECTALTNAME2' - Summary = '' - FindIt = {Find-ESC6} - FixIt = {Write-Output 'Add code to fix the vulnerable configuration.'} + Name = 'ESC6' + Category = 'Escalation Path' + Subcategory = 'EDITF_ATTRIBUTESUBJECTALTNAME2' + Summary = '' + FindIt = { Find-ESC6 } + FixIt = { Write-Output 'Add code to fix the vulnerable configuration.' } ReferenceUrls = 'https://posts.specterops.io/certified-pre-owned-d95910965cd2#:~:text=EDITF_ATTRIBUTESUBJECTALTNAME2%20%E2%80%94%20ESC6' }, [VulnerableConfigurationItem]@{ - Name = 'ESC7' - Category = 'Escalation Path' - Subcategory = 'Vulnerable Certificate Authority Access Control' - Summary = '' - FindIt = {Write-Output 'We have not created Find-ESC7 yet.'} - FixIt = {Write-Output 'Add code to fix the vulnerable configuration.'} + Name = 'ESC7' + Category = 'Escalation Path' + Subcategory = 'Vulnerable Certificate Authority Access Control' + Summary = '' + FindIt = { Write-Output 'We have not created Find-ESC7 yet.' } + FixIt = { Write-Output 'Add code to fix the vulnerable configuration.' } ReferenceUrls = 'https://posts.specterops.io/certified-pre-owned-d95910965cd2#:~:text=Vulnerable%20Certificate%20Authority%20Access%20Control%20%E2%80%94%20ESC7' }, [VulnerableConfigurationItem]@{ - Name = 'ESC8' - Category = 'Escalation Path' - Subcategory = 'AD CS HTTP Endpoints Vulnerable to NTLM Relay' - Summary = '' - FindIt = {Find-ESC8} - FixIt = {Write-Output 'Add code to fix the vulnerable configuration.'} + Name = 'ESC8' + Category = 'Escalation Path' + Subcategory = 'AD CS HTTP Endpoints Vulnerable to NTLM Relay' + Summary = '' + FindIt = { Find-ESC8 } + FixIt = { Write-Output 'Add code to fix the vulnerable configuration.' } ReferenceUrls = 'https://posts.specterops.io/certified-pre-owned-d95910965cd2#:~:text=NTLM%20Relay%20to%20AD%20CS%20HTTP%20Endpoints' }, # [VulnerableConfigurationItem]@{ @@ -126,39 +126,39 @@ function New-Dictionary { # ReferenceUrls = '' # }, [VulnerableConfigurationItem]@{ - Name = 'ESC11' - Category = 'Escalation Path' - Subcategory = 'IF_ENFORCEENCRYPTICERTREQUEST' - Summary = '' - FindIt = {Find-ESC11} - FixIt = {Write-Output 'Add code to fix the vulnerable configuration.'} + Name = 'ESC11' + Category = 'Escalation Path' + Subcategory = 'IF_ENFORCEENCRYPTICERTREQUEST' + Summary = '' + FindIt = { Find-ESC11 } + FixIt = { Write-Output 'Add code to fix the vulnerable configuration.' } ReferenceUrls = 'https://blog.compass-security.com/2022/11/relaying-to-ad-certificate-services-over-rpc/' }, [VulnerableConfigurationItem]@{ - Name = 'ESC13' - Category = 'Escalation Path' - Subcategory = 'Certificate Template linked to Group' - Summary = '' - FindIt = {Find-ESC13} - FixIt = {Write-Output 'Add code to fix the vulnerable configuration.'} + Name = 'ESC13' + Category = 'Escalation Path' + Subcategory = 'Certificate Template linked to Group' + Summary = '' + FindIt = { Find-ESC13 } + FixIt = { Write-Output 'Add code to fix the vulnerable configuration.' } ReferenceUrls = 'https://posts.specterops.io/adcs-esc13-abuse-technique-fda4272fbd53' - },[VulnerableConfigurationItem]@{ - Name = 'ESC15/EKUwu' - Category = 'Escalation Path' - Subcategory = 'Certificate Template using Schema V1' - Summary = '' - FindIt = {Find-ESC15} - FixIt = {Write-Output 'Add code to fix the vulnerable configuration.'} + }, [VulnerableConfigurationItem]@{ + Name = 'ESC15/EKUwu' + Category = 'Escalation Path' + Subcategory = 'Certificate Template using Schema V1' + Summary = '' + FindIt = { Find-ESC15 } + FixIt = { Write-Output 'Add code to fix the vulnerable configuration.' } ReferenceUrls = 'https://trustedsec.com/blog/ekuwu-not-just-another-ad-cs-esc' }, [VulnerableConfigurationItem]@{ - Name = 'Auditing' - Category = 'Server Configuration' - Subcategory = 'Gaps in auditing on certificate authorities and AD CS objects.' - Summary = '' - FindIt = {Find-AuditingIssue} - FixIt = {Write-Output 'Add code to fix the vulnerable configuration.'} - ReferenceUrls = @('https://github.com/TrimarcJake/Locksmith','https://techcommunity.microsoft.com/t5/ask-the-directory-services-team/designing-and-implementing-a-pki-part-i-design-and-planning/ba-p/396953') + Name = 'Auditing' + Category = 'Server Configuration' + Subcategory = 'Gaps in auditing on certificate authorities and AD CS objects.' + Summary = '' + FindIt = { Find-AuditingIssue } + FixIt = { Write-Output 'Add code to fix the vulnerable configuration.' } + ReferenceUrls = @('https://github.com/TrimarcJake/Locksmith', 'https://techcommunity.microsoft.com/t5/ask-the-directory-services-team/designing-and-implementing-a-pki-part-i-design-and-planning/ba-p/396953') } ) Return $Dictionary diff --git a/Private/Test-IsElevated.ps1 b/Private/Test-IsElevated.ps1 index 3f65b45..d945a96 100644 --- a/Private/Test-IsElevated.ps1 +++ b/Private/Test-IsElevated.ps1 @@ -11,7 +11,7 @@ .EXAMPLE # Prompt to launch elevated if not already running as administrator: if (!(Test-IsElevated)) { - $arguments = "& '" + $myinvocation.mycommand.definition + "'" + $arguments = "& '" + $MyInvocation.MyCommand.definition + "'" Start-Process powershell -Verb runAs -ArgumentList $arguments Break } @@ -19,4 +19,4 @@ $identity = [Security.Principal.WindowsIdentity]::GetCurrent() $principal = New-Object Security.Principal.WindowsPrincipal $identity $principal.IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator) -} \ No newline at end of file +} diff --git a/Public/Invoke-Locksmith.ps1 b/Public/Invoke-Locksmith.ps1 index 1bde0f8..ba460f2 100644 --- a/Public/Invoke-Locksmith.ps1 +++ b/Public/Invoke-Locksmith.ps1 @@ -1,4 +1,4 @@ -function Invoke-Locksmith { +function Invoke-Locksmith { <# .SYNOPSIS Finds the most common malconfigurations of Active Directory Certificate Services (AD CS). @@ -42,7 +42,7 @@ Specify which scans you want to run. Available scans: 'All' or Auditing, ESC1, ESC2, ESC3, ESC4, ESC5, ESC6, ESC8, or 'PromptMe' -Scans All - Run all scans (default) + Run all scans (default). -Scans PromptMe Presents a grid view of the available scan types that can be selected and run them after you click OK. @@ -55,16 +55,26 @@ .OUTPUTS Output types: - 1. Console display of identified issues - 2. Console display of identified issues and their fixes - 3. CSV containing all identified issues - 4. CSV containing all identified issues and their fixes + 1. Console display of identified issues. + 2. Console display of identified issues and their fixes. + 3. CSV containing all identified issues. + 4. CSV containing all identified issues and their fixes. + + .EXAMPLE + Invoke-Locksmith -Mode 0 -Scans All -OutputPath 'C:\Temp' + + Finds all malconfigurations and displays them in the console. + + .EXAMPLE + Invoke-Locksmith -Mode 2 -Scans All -OutputPath 'C:\Temp' + + Finds all malconfigurations and displays them in the console. The findings are saved in a CSV file in C:\Temp. .NOTES - Windows PowerShell cmdlet Restart-Service requires RunAsAdministrator + The Windows PowerShell cmdlet Restart-Service requires RunAsAdministrator. #> - [CmdletBinding()] + [CmdletBinding(HelpUri = 'https://trimarcjake.github.io/Locksmith/Invoke-Locksmith')] param ( #[string]$Forest, # Not used yet #[string]$InputPath, # Not used yet @@ -104,17 +114,17 @@ ) $Version = '' - $LogoPart1 = @" + $LogoPart1 = @' _ _____ _______ _ _ _______ _______ _____ _______ _ _ | | | | |____/ |______ | | | | | |_____| |_____ |_____| |_____ | \_ ______| | | | __|__ | | | -"@ - $LogoPart2 = @" +'@ + $LogoPart2 = @' .--. .--. .--. /.-. '----------. /.-. '----------. /.-. '----------. \'-' .---'-''-'-' \'-' .--'--''-'-' \'-' .--'--'-''-' '--' '--' '--' -"@ +'@ $VersionBanner = " v$Version" Write-Host $LogoPart1 -ForegroundColor Magenta @@ -137,7 +147,7 @@ ### Initial variables # For output filenames - [string]$FilePrefix = "Locksmith $(Get-Date -format 'yyyy-MM-dd hh-mm-ss')" + [string]$FilePrefix = "Locksmith $(Get-Date -Format 'yyyy-MM-dd hh-mm-ss')" # Extended Key Usages for client authentication. A requirement for ESC1, ESC3 Condition 2, and ESC13 $ClientAuthEKUs = '1\.3\.6\.1\.5\.5\.7\.3\.2|1\.3\.6\.1\.5\.2\.3\.4|1\.3\.6\.1\.4\.1\.311\.20\.2\.2|2\.5\.29\.37\.0' @@ -190,7 +200,7 @@ # $Dictionary = New-Dictionary $Forest = Get-ADForest - $ForestGC = $(Get-ADDomainController -Discover -Service GlobalCatalog -ForceDiscover | Select-Object -ExpandProperty Hostname) + ":3268" + $ForestGC = $(Get-ADDomainController -Discover -Service GlobalCatalog -ForceDiscover | Select-Object -ExpandProperty Hostname) + ':3268' # $DNSRoot = [string]($Forest.RootDomain | Get-ADDomain).DNSRoot $EnterpriseAdminsSID = ([string]($Forest.RootDomain | Get-ADDomain).DomainSID) + '-519' $PreferredOwner = [System.Security.Principal.SecurityIdentifier]::New($EnterpriseAdminsSID) diff --git a/en-US/Locksmith-help.xml b/en-US/Locksmith-help.xml new file mode 100644 index 0000000..f23ba60 --- /dev/null +++ b/en-US/Locksmith-help.xml @@ -0,0 +1,207 @@ + + + + + Invoke-Locksmith + Invoke + Locksmith + + Finds the most common malconfigurations of Active Directory Certificate Services (AD CS). + + + + Locksmith uses the Active Directory (AD) Powershell (PS) module to identify 10 misconfigurations commonly found in Enterprise mode AD CS installations. + + + + Invoke-Locksmith + + Mode + + Specifies sets of common script execution modes. + -Mode 0 Finds any malconfigurations and displays them in the console. No attempt is made to fix identified issues. + -Mode 1 Finds any malconfigurations and displays them in the console. Displays example Powershell snippet that can be used to resolve the issue. No attempt is made to fix identified issues. + -Mode 2 Finds any malconfigurations and writes them to a series of CSV files. No attempt is made to fix identified issues. + -Mode 3 Finds any malconfigurations and writes them to a series of CSV files. Creates code snippets to fix each issue and writes them to an environment-specific custom .PS1 file. No attempt is made to fix identified issues. + -Mode 4 Finds any malconfigurations and creates code snippets to fix each issue. Attempts to fix all identified issues. This mode may require high-privileged access. + + Int32 + + Int32 + + + 0 + + + Scans + + Specify which scans you want to run. Available scans: 'All' or Auditing, ESC1, ESC2, ESC3, ESC4, ESC5, ESC6, ESC8, or 'PromptMe' + -Scans All Run all scans (default). + -Scans PromptMe Presents a grid view of the available scan types that can be selected and run them after you click OK. + + Array + + Array + + + All + + + OutputPath + + Specify the path where you want to save reports and mitigation scripts. + + String + + String + + + $PWD + + + Credential + + The credential to use for working with ADCS. + + PSCredential + + PSCredential + + + None + + + + + + Mode + + Specifies sets of common script execution modes. + -Mode 0 Finds any malconfigurations and displays them in the console. No attempt is made to fix identified issues. + -Mode 1 Finds any malconfigurations and displays them in the console. Displays example Powershell snippet that can be used to resolve the issue. No attempt is made to fix identified issues. + -Mode 2 Finds any malconfigurations and writes them to a series of CSV files. No attempt is made to fix identified issues. + -Mode 3 Finds any malconfigurations and writes them to a series of CSV files. Creates code snippets to fix each issue and writes them to an environment-specific custom .PS1 file. No attempt is made to fix identified issues. + -Mode 4 Finds any malconfigurations and creates code snippets to fix each issue. Attempts to fix all identified issues. This mode may require high-privileged access. + + Int32 + + Int32 + + + 0 + + + Scans + + Specify which scans you want to run. Available scans: 'All' or Auditing, ESC1, ESC2, ESC3, ESC4, ESC5, ESC6, ESC8, or 'PromptMe' + -Scans All Run all scans (default). + -Scans PromptMe Presents a grid view of the available scan types that can be selected and run them after you click OK. + + Array + + Array + + + All + + + OutputPath + + Specify the path where you want to save reports and mitigation scripts. + + String + + String + + + $PWD + + + Credential + + The credential to use for working with ADCS. + + PSCredential + + PSCredential + + + None + + + + + + None. You cannot pipe objects to Invoke-Locksmith.ps1. + + + + + + + + + + Output types: + + + + + + + + 1. Console display of identified issues. + + + + + + + + 2. Console display of identified issues and their fixes. + + + + + + + + 3. CSV containing all identified issues. + + + + + + + + 4. CSV containing all identified issues and their fixes. + + + + + + + + + The Windows PowerShell cmdlet Restart-Service requires RunAsAdministrator. + + + + + -------------------------- EXAMPLE 1 -------------------------- + Invoke-Locksmith -Mode 0 -Scans All -OutputPath 'C:\Temp' + + Finds all malconfigurations and displays them in the console. + + + + -------------------------- EXAMPLE 2 -------------------------- + Invoke-Locksmith -Mode 2 -Scans All -OutputPath 'C:\Temp' + + Finds all malconfigurations and displays them in the console. The findings are saved in a CSV file in C:\Temp. + + + + + + \ No newline at end of file diff --git a/en-US/Locksmith_b1325b42-8dc4-4f17-aa1f-dcb5984ca14a_HelpInfo.xml b/en-US/Locksmith_b1325b42-8dc4-4f17-aa1f-dcb5984ca14a_HelpInfo.xml new file mode 100644 index 0000000..7d58517 --- /dev/null +++ b/en-US/Locksmith_b1325b42-8dc4-4f17-aa1f-dcb5984ca14a_HelpInfo.xml @@ -0,0 +1,10 @@ + + + NA + + + en-US + 2024.11.11 + + + \ No newline at end of file diff --git a/en-US/Locksmith_b1325b42-8dc4-4f17-aa1f-dcb5984ca14a_en-US_HelpContent.cab b/en-US/Locksmith_b1325b42-8dc4-4f17-aa1f-dcb5984ca14a_en-US_HelpContent.cab new file mode 100644 index 0000000..954969f Binary files /dev/null and b/en-US/Locksmith_b1325b42-8dc4-4f17-aa1f-dcb5984ca14a_en-US_HelpContent.cab differ diff --git a/en-US/Locksmith_b1325b42-8dc4-4f17-aa1f-dcb5984ca14a_en-US_HelpContent.zip b/en-US/Locksmith_b1325b42-8dc4-4f17-aa1f-dcb5984ca14a_en-US_HelpContent.zip new file mode 100644 index 0000000..7baaecf Binary files /dev/null and b/en-US/Locksmith_b1325b42-8dc4-4f17-aa1f-dcb5984ca14a_en-US_HelpContent.zip differ diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..da8c1e4 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,91 @@ +# https://www.mkdocs.org/user-guide/configuration/ +# https://www.mkdocs.org/user-guide/writing-your-docs/ +# https://www.mkdocs.org/user-guide/writing-your-docs/#writing-with-markdown +# https://mkdocs.readthedocs.io/en/0.15.2/user-guide/writing-your-docs/ +# https://mkdocs.readthedocs.io/en/0.15.2/user-guide/styling-your-docs/ +# https://example-mkdocs-basic.readthedocs.io/en/latest/ +# https://github.com/mkdocs/mkdocs/blob/master/mkdocs.yml +# https://squidfunk.github.io/mkdocs-material/creating-your-site/ +# mkdocs.yml + +site_name: "Locksmith" +site_url: "https://dotdot.horse/Locksmith" +repo_url: "https://github.com/TrimarcJake/Locksmith" +repo_name: "TrimarcJake/Locksmith" +# edit_uri: edit/main/docs/ +# edit_uri_template: +site_description: "A small tool to find and fix common misconfigurations in Active Directory Certificate Services." # meta tag to the generated HTML heade +site_author: "Jake Hildreth" # meta tag to the generated HTML header +copyright: "(c) 2024 Jake Hildreth." +# remote_branch: gh-deploy +# remote_name: +docs_dir: "docs" +# site_dir: +# extra_css: +# extra_javascript: +# markdown_extensions: +# Python Markdown +# - admonition +# - toc: +# permalink: true +# code highlighting +# - pymdownx.highlight: +# use_pygments: true +# - pymdownx.highlight: +# anchor_linenums: true +# - pymdownx.inlinehilite +# - pymdownx.snippets +# - pymdownx.superfences + +# extra_templates: +# extra: + +#plugins: +# - social + +theme: + name: material + highlightjs: true + hljs_languages: + - yaml + - powershell + palette: + # Palette toggle for automatic mode + - media: "(prefers-color-scheme)" + toggle: + icon: material/brightness-auto + name: Switch to light mode + + # Palette toggle for light mode + - media: "(prefers-color-scheme: light)" + scheme: default + toggle: + icon: material/brightness-7 + name: Switch to dark mode + + # Palette toggle for dark mode + - media: "(prefers-color-scheme: dark)" + scheme: slate + toggle: + icon: material/brightness-4 + name: Switch to system preference + + language: en + # custom_dir: overrides + features: + - navigation.tabs + - navigation.tabs.sticky + - navigation.path + - navigation.instant + - navigation.instant.prefetch + - navigation.tracking + - navigation.sections + - navigation.top + - toc.follow + - toc.integrate + # favicon: + # icon: + # repo: + # font: + # text: Work Sans + # logo: