Repository: h4wkst3r/SCMKit Branch: main Commit: 138a811b6512 Files: 61 Total size: 407.0 KB Directory structure: gitextract_g9j8xacg/ ├── .gitignore ├── Detections/ │ ├── SCMKit.rules │ └── SCMKit.yar ├── LICENSE ├── README.md ├── SCMKit/ │ ├── App.config │ ├── FodyWeavers.xml │ ├── Properties/ │ │ └── AssemblyInfo.cs │ ├── SCMKit.cs │ ├── SCMKit.csproj │ ├── library/ │ │ ├── BitbucketUtils.cs │ │ ├── GitHubUtils.cs │ │ ├── GitLabUtils.cs │ │ ├── Utils.cs │ │ └── WebUtils.cs │ ├── modules/ │ │ ├── bitbucket/ │ │ │ ├── AddAdmin.cs │ │ │ ├── CodeSearch.cs │ │ │ ├── CreatePAT.cs │ │ │ ├── CreateSSHKey.cs │ │ │ ├── FileSearch.cs │ │ │ ├── ListPAT.cs │ │ │ ├── ListSSHKeys.cs │ │ │ ├── RemoveAdmin.cs │ │ │ ├── RemovePAT.cs │ │ │ ├── RemoveSSHKey.cs │ │ │ ├── RepoList.cs │ │ │ └── RepoSearch.cs │ │ ├── github/ │ │ │ ├── AddAdmin.cs │ │ │ ├── AdminStats.cs │ │ │ ├── BranchProtection.cs │ │ │ ├── CodeSearch.cs │ │ │ ├── CreatePAT.cs │ │ │ ├── CreateSSHKey.cs │ │ │ ├── FileSearch.cs │ │ │ ├── GistList.cs │ │ │ ├── ListPAT.cs │ │ │ ├── ListSSHKeys.cs │ │ │ ├── OrgList.cs │ │ │ ├── Privs.cs │ │ │ ├── RemoveAdmin.cs │ │ │ ├── RemovePAT.cs │ │ │ ├── RemoveSSHKey.cs │ │ │ ├── RepoList.cs │ │ │ └── RepoSearch.cs │ │ └── gitlab/ │ │ ├── AddAdmin.cs │ │ ├── CodeSearch.cs │ │ ├── CreatePAT.cs │ │ ├── CreateSSHKey.cs │ │ ├── FileSearch.cs │ │ ├── ListPAT.cs │ │ ├── ListSSHKeys.cs │ │ ├── Privs.cs │ │ ├── RemoveAdmin.cs │ │ ├── RemovePAT.cs │ │ ├── RemoveSSHKey.cs │ │ ├── RepoList.cs │ │ ├── RepoSearch.cs │ │ ├── RunnerList.cs │ │ └── SnippetList.cs │ └── packages.config └── SCMKit.sln ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. ## ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore *.exe # User-specific files *.rsuser *.suo *.user *.userosscache *.sln.docstates # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs # Mono auto generated files mono_crash.* # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ x64/ x86/ [Ww][Ii][Nn]32/ [Aa][Rr][Mm]/ [Aa][Rr][Mm]64/ bld/ [Bb]in/ [Oo]bj/ [Ll]og/ [Ll]ogs/ # Visual Studio 2015/2017 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ # Visual Studio 2017 auto generated files Generated\ Files/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUnit *.VisualState.xml TestResult.xml nunit-*.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c # Benchmark Results BenchmarkDotNet.Artifacts/ # .NET Core project.lock.json project.fragment.lock.json artifacts/ # ASP.NET Scaffolding ScaffoldingReadMe.txt # StyleCop StyleCopReport.xml # Files built by Visual Studio *_i.c *_p.c *_h.h *.ilk *.meta *.obj *.iobj *.pch *.pdb *.ipdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *_wpftmp.csproj *.log *.tlog *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opendb *.opensdf *.sdf *.cachefile *.VC.db *.VC.VC.opendb # Visual Studio profiler *.psess *.vsp *.vspx *.sap # Visual Studio Trace Files *.e2e # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # AxoCover is a Code Coverage Tool .axoCover/* !.axoCover/settings.json # Coverlet is a free, cross platform Code Coverage Tool coverage*.json coverage*.xml coverage*.info # Visual Studio code coverage results *.coverage *.coveragexml # NCrunch _NCrunch_* .*crunch*.local.xml nCrunchTemp_* # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # Note: Comment the next line if you want to checkin your web deploy settings, # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj # Microsoft Azure Web App publish settings. Comment the next line if you want to # checkin your Azure Web App publish settings, but sensitive information contained # in these scripts will be unencrypted PublishScripts/ # NuGet Packages *.nupkg # NuGet Symbol Packages *.snupkg # The packages folder can be ignored because of Package Restore [Pp]ackages/* # except build/, which is used as an MSBuild target. ![Pp]ackages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/[Pp]ackages/repositories.config # NuGet v3's project.json files produces more ignorable files *.nuget.props *.nuget.targets # Nuget personal access tokens and Credentials nuget.config # Microsoft Azure Build Output csx/ *.build.csdef # Microsoft Azure Emulator ecf/ rcf/ # Windows Store app package directories and files AppPackages/ BundleArtifacts/ Package.StoreAssociation.xml _pkginfo.txt *.appx *.appxbundle *.appxupload # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !?*.[Cc]ache/ # Others ClientBin/ ~$* *~ *.dbmdl *.dbproj.schemaview *.jfm *.pfx *.publishsettings orleans.codegen.cs # Including strong name files can present a security risk # (https://github.com/github/gitignore/pull/2483#issue-259490424) #*.snk # Since there are multiple workflows, uncomment next line to ignore bower_components # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) #bower_components/ # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm ServiceFabricBackup/ *.rptproj.bak # SQL Server files *.mdf *.ldf *.ndf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings *.rptproj.rsuser *- [Bb]ackup.rdl *- [Bb]ackup ([0-9]).rdl *- [Bb]ackup ([0-9][0-9]).rdl # Microsoft Fakes FakesAssemblies/ # GhostDoc plugin setting file *.GhostDoc.xml # Node.js Tools for Visual Studio .ntvs_analysis.dat node_modules/ # Visual Studio 6 build log *.plg # Visual Studio 6 workspace options file *.opt # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) *.vbw # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/ModelManifest.xml **/*.Server/GeneratedArtifacts **/*.Server/ModelManifest.xml _Pvt_Extensions # Paket dependency manager .paket/paket.exe paket-files/ # FAKE - F# Make .fake/ # CodeRush personal settings .cr/personal # Python Tools for Visual Studio (PTVS) __pycache__/ *.pyc # Cake - Uncomment if you are using it # tools/** # !tools/packages.config # Tabs Studio *.tss # Telerik's JustMock configuration file *.jmconfig # BizTalk build output *.btp.cs *.btm.cs *.odx.cs *.xsd.cs # OpenCover UI analysis results OpenCover/ # Azure Stream Analytics local run output ASALocalRun/ # MSBuild Binary and Structured Log *.binlog # NVidia Nsight GPU debugger configuration file *.nvuser # MFractors (Xamarin productivity tool) working folder .mfractor/ # Local History for Visual Studio .localhistory/ # BeatPulse healthcheck temp database healthchecksdb # Backup folder for Package Reference Convert tool in Visual Studio 2017 MigrationBackup/ # Ionide (cross platform F# VS Code tools) working folder .ionide/ # Fody - auto-generated XML schema FodyWeavers.xsd # VS Code files for those working on multiple tools .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json *.code-workspace # Local History for Visual Studio Code .history/ # Windows Installer files from build outputs *.cab *.msi *.msix *.msm *.msp # JetBrains Rider .idea/ *.sln.iml ================================================ FILE: Detections/SCMKit.rules ================================================ alert tcp $HOME_NET any -> any $HTTP_PORTS (flow:established,to_server; content:"SCMKIT-5dc493ada400c79dd318abbe770dac7c"; http_header; fast_pattern:only; pcre:"/^User\x2dAgent\x3a\x20SCMKit/Hm"; metadata:service http; msg:"Known malicious user-agent string SCMKit tool"; id:5493400793187707; rev:1; ) ================================================ FILE: Detections/SCMKit.yar ================================================ rule SCMKit_Signatures { meta: description = "Static signatures for the SCMKit tool." md5 = "9b4b2a06aa840afcbbfe2d412f99b4a8" rev = 1 author = "Brett Hawkins" strings: $typelibguid = "266c644a-69b1-426b-a47c-1cf32b211f80" ascii nocase wide $gitlabModules = "SCMKit.modules.gitlab" ascii nocase wide $githubModules = "SCMKit.modules.github" ascii nocase wide $bitbucketModules = "SCMKit.modules.bitbucket" ascii nocase wide condition: uint16(0) == 0x5A4D and $typelibguid and $gitlabModules and $githubModules and $bitbucketModules } ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2022 Brett Hawkins Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README.md ================================================ # SCMKit ## Description **S**ource **C**ode **M**anagement Attack Tool**kit** - SCMKit is a toolkit that can be used to attack SCM systems. SCMKit allows the user to specify the SCM system and attack module to use, along with specifying valid credentials (username/password or API key) to the respective SCM system. Currently, the SCM systems that SCMKit supports are GitHub Enterprise, GitLab Enterprise and Bitbucket Server. The attack modules supported include reconnaissance, privilege escalation and persistence. SCMKit was built in a modular approach, so that new modules and SCM systems can be added in the future by the information security community. ## Release * Version 1.2 of SCMKit can be found in Releases ## Table of Contents - [SCMKit](#scmkit) - [Table of Contents](#table-of-contents) - [Installation/Building](#installationbuilding) - [Libraries Used](#libraries-used) - [Pre-Compiled](#pre-compiled) - [Building Yourself](#building-yourself) - [Usage](#usage) - [Arguments/Options](#argumentsoptions) - [Systems](#systems--s--system) - [Modules](#modules--m--module) - [Module Details Table](#Module-Details-Table) - [Examples](#examples) - [List Repos](#List-repos) - [Search Repos](#Search-repos) - [Search Code](#Search-code) - [Search Files](#Search-files) - [List Snippets](#List-snippets) - [List Runners](#List-runners) - [List Gists](#List-gists) - [List Orgs](#List-orgs) - [Get Privileges of API Key](#Get-privileges-of-api-token) - [Add Admin](#Add-admin) - [Remove Admin](#Remove-admin) - [Create Access Token](#Create-access-token) - [List Access Tokens](#List-access-tokens) - [Remove Access Token](#Remove-access-token) - [Create SSH Key](#Create-ssh-key) - [List SSH Keys](#List-ssh-keys) - [Remove SSH Key](#Remove-ssh-key) - [List Admin Stats](#list-admin-stats) - [List Branch Protection](#list-branch-protection) - [Detection](#detection) - [References](#references) ## Installation/Building ### Libraries Used The below 3rd party libraries are used in this project. | Library | URL | License | | ------------- | ------------- | ------------- | | Octokit | [https://github.com/octokit/octokit.net](https://github.com/octokit/octokit.net) | MIT License | | Fody | [https://github.com/Fody/Fody](https://github.com/Fody/Fody) | MIT License | | GitLabApiClient | [https://github.com/nmklotas/GitLabApiClient](https://github.com/nmklotas/GitLabApiClient) | MIT License | | Newtonsoft.Json | [https://github.com/JamesNK/Newtonsoft.Json](https://github.com/JamesNK/Newtonsoft.Json) | MIT License | ### Pre-Compiled * Use the pre-compiled binary in Releases ### Building Yourself Take the below steps to setup Visual Studio in order to compile the project yourself. This requires a .NET library that can be installed from the NuGet package manager. * Load the Visual Studio project up and go to "Tools" --> "NuGet Package Manager" --> "Package Manager Settings" * Go to "NuGet Package Manager" --> "Package Sources" * Add a package source with the URL `https://api.nuget.org/v3/index.json` * Install the below NuGet packages * `Install-Package Costura.Fody -Version 3.3.3` * `Install-Package Octokit` * `Install-Package GitLabApiClient` * `Install-Package Newtonsoft.Json` * You can now build the project yourself! ## Usage ### Arguments/Options * -c, -credential - credential for authentication (username:password or apiKey) * -s, -system - system to attack (github,gitlab,bitbucket) * -u, -url - URL for GitHub Enterprise, GitLab Enterprise or Bitbucket Server * -m, -module - module to run * -o, -option - options (when applicable) ### Systems (-s, -system) * github: GitHub Enterprise * gitlab: GitLab Enterprise * bitbucket: Bitbucket Server ### Modules (-m, -module) * listrepo: list all repos the current user can see * searchrepo: search for a given repo * searchcode: search for code containing keyword search term * searchfile: search for filename containing keyword search term * listsnippet: list all snippets of current user * listrunner: list all GitLab runners available to current user * listgist: list all gists of current user * listorg: list all orgs current user belongs to * privs: get privs of current API token * addadmin: promote given user to admin role * removeadmin: demote given user from admin role * createpat: create personal access token for target user * listpat: list personal access tokens for a target user * removepat: remove personal access token for a target user * createsshkey: create SSH key for current user * listsshkey: list SSH keys for current user * removesshkey: remove SSH key for current user * adminstats: get admin stats (users, repos, orgs, gists) * protection: get branch protection settings ### Module Details Table The below table shows where each module is supported Attack Scenario | Module | Requires Admin? | GitHub Enterprise | GitLab Enterprise | Bitbucket Server :---: |:---: | :---: | :---: | :---: | :---: Reconnaissance | `listrepo` | No | X | X | X Reconnaissance |`searchrepo` | No | X | X | X Reconnaissance |`searchcode` | No | X | X | X Reconnaissance |`searchfile` | No | X | X | X Reconnaissance |`listsnippet` | No | | X | Reconnaissance |`listrunner` | No | | X | Reconnaissance |`listgist` | No | X | | Reconnaissance |`listorg` | No | X | | Reconnaissance |`privs` | No | X | X | Reconnaissance |`protection` | No | X | | Persistence | `listsshkey` | No | X | X | X Persistence | `removesshkey` | No | X | X | X Persistence | `createsshkey` | No | X | X | X Persistence | `listpat` | No | | X | X Persistence | `removepat` | No | | X | X Persistence | `createpat` | Yes (GitLab Enterprise only) | | X | X Privilege Escalation | `addadmin` | Yes | X | X | X Privilege Escalation | `removeadmin` | Yes | X | X | X Reconnaissance | `adminstats` | Yes | X | | ## Examples ### List Repos #### Use Case > *Discover repositories being used in a particular SCM system* #### Syntax Provide the `listrepo` module, along with any relevant authentication information and URL. This will output the repository name and URL. ##### GitHub Enterprise This will list all repositories that a user can see. `SCMKit.exe -s github -m listrepo -c userName:password -u https://github.something.local` `SCMKit.exe -s github -m listrepo -c apiKey -u https://github.something.local` ##### GitLab Enterprise This will list all repositories that a user can see. `SCMKit.exe -s gitlab -m listrepo -c userName:password -u https://gitlab.something.local` `SCMKit.exe -s gitlab -m listrepo -c apiKey -u https://gitlab.something.local` ##### Bitbucket Server This will list all repositories that a user can see. `SCMKit.exe -s bitbucket -m listrepo -c userName:password -u https://bitbucket.something.local` `SCMKit.exe -s bitbucket -m listrepo -c apiKey -u https://bitbucket.something.local` #### Example Output ``` C:\>SCMKit.exe -s gitlab -m listrepo -c username:password -u https://gitlab.hogwarts.local ================================================== Module: listrepo System: gitlab Auth Type: Username/Password Options: Target URL: https://gitlab.hogwarts.local Timestamp: 1/14/2022 8:30:47 PM ================================================== Name | Visibility | URL ---------------------------------------------------------------------------------------------------------- MaraudersMap | Private | https://gitlab.hogwarts.local/hpotter/maraudersmap testingStuff | Internal | https://gitlab.hogwarts.local/adumbledore/testingstuff Spellbook | Internal | https://gitlab.hogwarts.local/hpotter/spellbook findShortestPathToGryffindorSword | Internal | https://gitlab.hogwarts.local/hpotter/findShortestPathToGryffindorSword charms | Public | https://gitlab.hogwarts.local/hgranger/charms Secret-Spells | Internal | https://gitlab.hogwarts.local/adumbledore/secret-spells Monitoring | Internal | https://gitlab.hogwarts.local/gitlab-instance-10590c85/Monitoring ``` ### Search Repos #### Use Case > *Search for repositories by repository name in a particular SCM system* #### Syntax Provide the `searchrepo` module and your search criteria in the `-o` command-line switch, along with any relevant authentication information and URL. This will output the matching repository name and URL. ##### GitHub Enterprise The GitHub repo search is a "contains" search where the string you enter it will search for repos with names that contain your search term. `SCMKit.exe -s github -m searchrepo -c userName:password -u https://github.something.local -o "some search term"` `SCMKit.exe -s github -m searchrepo -c apikey -u https://github.something.local -o "some search term"` ##### GitLab Enterprise The GitLab repo search is a "contains" search where the string you enter it will search for repos with names that contain your search term. `SCMKit.exe -s gitlab -m searchrepo -c userName:password -u https://gitlab.something.local -o "some search term"` `SCMKit.exe -s gitlab -m searchrepo -c apikey -u https://gitlab.something.local -o "some search term"` ##### Bitbucket Server The Bitbucket repo search is a "starts with" search where the string you enter it will search for repos with names that start with your search term. `SCMKit.exe -s bitbucket -m searchrepo -c userName:password -u https://bitbucket.something.local -o "some search term"` `SCMKit.exe -s bitbucket -m searchrepo -c apikey -u https://bitbucket.something.local -o "some search term"` #### Example Output ``` C:\>SCMKit.exe -s gitlab -m searchrepo -c apiKey -u https://gitlab.hogwarts.local -o "spell" ================================================== Module: searchrepo System: gitlab Auth Type: API Key Options: spell Target URL: https://gitlab.hogwarts.local Timestamp: 1/14/2022 8:32:30 PM ================================================== Name | Visibility | URL ---------------------------------------------------------------------------------------------------------- Spellbook | Internal | https://gitlab.hogwarts.local/hpotter/spellbook Secret-Spells | Internal | https://gitlab.hogwarts.local/adumbledore/secret-spells ``` ### Search Code #### Use Case > *Search for code containing a given keyword in a particular SCM system* #### Syntax Provide the `searchcode` module and your search criteria in the `-o` command-line switch, along with any relevant authentication information and URL. This will output the URL to the matching code file, along with the line in the code that matched. ##### GitHub Enterprise The GitHub code search is a "contains" search where the string you enter it will search for code that contains your search term in any line. `SCMKit.exe -s github -m searchcode -c userName:password -u https://github.something.local -o "some search term"` `SCMKit.exe -s github -m searchcode -c apikey -u https://github.something.local -o "some search term"` ##### GitLab Enterprise The GitLab code search is a "contains" search where the string you enter it will search for code that contains your search term in any line. `SCMKit.exe -s gitlab -m searchcode -c userName:password -u https://gitlab.something.local -o "some search term"` `SCMKit.exe -s gitlab -m searchcode -c apikey -u https://gitlab.something.local -o "some search term"` ##### Bitbucket Server The Bitbucket code search is a "contains" search where the string you enter it will search for code that contains your search term in any line. `SCMKit.exe -s bitbucket -m searchcode -c userName:password -u https://bitbucket.something.local -o "some search term"` `SCMKit.exe -s bitbucket -m searchcode -c apikey -u https://bitbucket.something.local -o "some search term"` #### Example Output ``` C:\>SCMKit.exe -s gitlab -m searchcode -c username:password -u https://gitlab.hogwarts.local -o "api_key" ================================================== Module: searchcode System: gitlab Auth Type: Username/Password Options: api_key Target URL: https://gitlab.hogwarts.local Timestamp: 1/14/2022 8:34:14 PM ================================================== [>] URL: https://gitlab.hogwarts.local/adumbledore/secret-spells/stuff.txt |_ API_KEY=abc123 Total number of items matching code search: 1 ``` ### Search Files #### Use Case > *Search for files in repositories containing a given keyword in the file name in a particular SCM system* #### Syntax Provide the `searchfile` module and your search criteria in the `-o` command-line switch, along with any relevant authentication information and URL. This will output the URL to the matching file in its respective repository. ##### GitHub Enterprise The GitLab file search is a "contains" search where the string you enter it will search for files that contains your search term in the file name. `SCMKit.exe -s github -m searchfile -c userName:password -u https://github.something.local -o "some search term"` `SCMKit.exe -s github -m searchfile -c apikey -u https://github.something.local -o "some search term"` ##### GitLab Enterprise The GitLab file search is a "contains" search where the string you enter it will search for files that contains your search term in the file name. `SCMKit.exe -s gitlab -m searchfile -c userName:password -u https://gitlab.something.local -o "some search term"` `SCMKit.exe -s gitlab -m searchfile -c apikey -u https://gitlab.something.local -o "some search term"` ##### Bitbucket Server The Bitbucket file search is a "contains" search where the string you enter it will search for files that contains your search term in the file name. `SCMKit.exe -s bitbucket -m searchfile -c userName:password -u https://bitbucket.something.local -o "some search term"` `SCMKit.exe -s bitbucket -m searchfile -c apikey -u https://bitbucket.something.local -o "some search term"` #### Example Output ``` C:\source\SCMKit\SCMKit\bin\Release>SCMKit.exe -s bitbucket -m searchfile -c apikey -u http://bitbucket.hogwarts.local:7990 -o jenkinsfile ================================================== Module: searchfile System: bitbucket Auth Type: API Key Options: jenkinsfile Target URL: http://bitbucket.hogwarts.local:7990 Timestamp: 1/14/2022 10:17:59 PM ================================================== [>] REPO: http://bitbucket.hogwarts.local:7990/scm/~HPOTTER/hpotter [>] FILE: Jenkinsfile [>] REPO: http://bitbucket.hogwarts.local:7990/scm/STUD/cred-decryption [>] FILE: subDir/Jenkinsfile Total matching results: 2 ``` ### List Snippets #### Use Case > *List snippets owned by the current user in GitLab* #### Syntax Provide the `listsnippet` module, along with any relevant authentication information and URL. ##### GitLab Enterprise `SCMKit.exe -s gitlab -m listsnippet -c userName:password -u https://gitlab.something.local` `SCMKit.exe -s gitlab -m listsnippet -c apikey -u https://gitlab.something.local` #### Example Output ``` C:\>SCMKit.exe -s gitlab -m listsnippet -c username:password -u https://gitlab.hogwarts.local ================================================== Module: listsnippet System: gitlab Auth Type: Username/Password Options: Target URL: https://gitlab.hogwarts.local Timestamp: 1/14/2022 9:17:36 PM ================================================== Title | Raw URL --------------------------------------------------------------------------------------------- spell-script | https://gitlab.hogwarts.local/-/snippets/2/raw ``` ### List Runners #### Use Case > *List all GitLab runners available to the current user in GitLab* #### Syntax Provide the `listrunner` module, along with any relevant authentication information and URL. If the user is an administrator, you will be able to list all runners within the GitLab Enterprise instance, which includes shared and group runners. ##### GitLab Enterprise `SCMKit.exe -s gitlab -m listrunner -c userName:password -u https://gitlab.something.local` `SCMKit.exe -s gitlab -m listrunner -c apikey -u https://gitlab.something.local` #### Example Output ``` C:\>SCMKit.exe -s gitlab -m listrunner -c username:password -u https://gitlab.hogwarts.local ================================================== Module: listrunner System: gitlab Auth Type: Username/Password Options: Target URL: https://gitlab.hogwarts.local Timestamp: 1/25/2022 11:40:08 AM ================================================== ID | Name | Repo Assigned --------------------------------------------------------------------------------- 2 | gitlab-runner | https://gitlab.hogwarts.local/hpotter/spellbook.git 3 | gitlab-runner | https://gitlab.hogwarts.local/hpotter/maraudersmap.git ``` ### List Gists #### Use Case > *List gists owned by the current user in GitHub* #### Syntax Provide the `listgist` module, along with any relevant authentication information and URL. ##### GitHub Enterprise `SCMKit.exe -s github -m listgist -c userName:password -u https://github.something.local` `SCMKit.exe -s github -m listgist -c apikey -u https://github.something.local` #### Example Output ``` C:\>SCMKit.exe -s github -m listgist -c username:password -u https://github-enterprise.hogwarts.local ================================================== Module: listgist System: github Auth Type: Username/Password Options: Target URL: https://github-enterprise.hogwarts.local Timestamp: 1/14/2022 9:43:23 PM ================================================== Description | Visibility | URL ---------------------------------------------------------------------------------------------------------- Shell Script to Decode Spell | public | https://github-enterprise.hogwarts.local/gist/c11c6bb3f47fe67183d5bc9f048412a1 ``` ### List Orgs #### Use Case > *List all organizations the current user belongs to in GitHub* #### Syntax Provide the `listorg` module, along with any relevant authentication information and URL. ##### GitHub Enterprise `SCMKit.exe -s github -m listorg -c userName:password -u https://github.something.local` `SCMKit.exe -s github -m listorg -c apiKey -u https://github.something.local` #### Example Output ``` C:\>SCMKit.exe -s github -m listorg -c username:password -u https://github-enterprise.hogwarts.local ================================================== Module: listorg System: github Auth Type: Username/Password Options: Target URL: https://github-enterprise.hogwarts.local Timestamp: 1/14/2022 9:44:48 PM ================================================== Name | URL ----------------------------------------------------------------------------------- Hogwarts | https://github-enterprise.hogwarts.local/api/v3/orgs/Hogwarts/repos ``` ### Get Privileges of API Token #### Use Case > *Get the assigned privileges to an access token being used in a particular SCM system* #### Syntax Provide the `privs` module, along with an API key and URL. ##### GitHub Enterprise `SCMKit.exe -s github -m privs -c apiKey -u https://github.something.local` ##### GitLab Enterprise `SCMKit.exe -s gitlab -m privs -c apiKey -u https://gitlab.something.local` #### Example Output ``` C:\>SCMKit.exe -s gitlab -m privs -c apikey -u https://gitlab.hogwarts.local ================================================== Module: privs System: gitlab Auth Type: API Key Options: Target URL: https://gitlab.hogwarts.local Timestamp: 1/14/2022 9:18:27 PM ================================================== Token Name | Active? | Privilege | Description --------------------------------------------------------------------------------------------------------------------------------- hgranger-api-token | True | api | Read-write for the complete API, including all groups and projects, the Container Registry, and the Package Registry. hgranger-api-token | True | read_user | Read-only for endpoints under /users. Essentially, access to any of the GET requests in the Users API. hgranger-api-token | True | read_api | Read-only for the complete API, including all groups and projects, the Container Registry, and the Package Registry. hgranger-api-token | True | read_repository | Read-only (pull) for the repository through git clone. hgranger-api-token | True | write_repository | Read-write (pull, push) for the repository through git clone. Required for accessing Git repositories over HTTP when 2FA is enabled. ``` ### Add Admin #### Use Case > *Promote a normal user to an administrative role in a particular SCM system* #### Syntax Provide the `addadmin` module, along with any relevant authentication information and URL. Additionally, provide the target user you would like to add an administrative role to. ##### GitHub Enterprise `SCMKit.exe -s github -m addadmin -c userName:password -u https://github.something.local -o targetUserName` `SCMKit.exe -s github -m addadmin -c apikey -u https://github.something.local -o targetUserName` ##### GitLab Enterprise `SCMKit.exe -s gitlab -m addadmin -c userName:password -u https://gitlab.something.local -o targetUserName` `SCMKit.exe -s gitlab -m addadmin -c apikey -u https://gitlab.something.local -o targetUserName` ##### Bitbucket Server Only username/password auth is supported to perform actions not related to repos or projects in Bitbucket. `SCMKit.exe -s bitbucket -m addadmin -c userName:password -u https://bitbucket.something.local -o targetUserName` #### Example Output ``` C:\>SCMKit.exe -s gitlab -m addadmin -c apikey -u https://gitlab.hogwarts.local -o hgranger ================================================== Module: addadmin System: gitlab Auth Type: API Key Options: hgranger Target URL: https://gitlab.hogwarts.local Timestamp: 1/14/2022 9:19:32 PM ================================================== [+] SUCCESS: The hgranger user was successfully added to the admin role. ``` ### Remove Admin #### Use Case > *Demote an administrative user to a normal user role in a particular SCM system* #### Syntax Provide the `removeadmin` module, along with any relevant authentication information and URL. Additionally, provide the target user you would like to remove an administrative role from. ##### GitHub Enterprise `SCMKit.exe -s github -m removeadmin -c userName:password -u https://github.something.local -o targetUserName` `SCMKit.exe -s github -m removeadmin -c apikey -u https://github.something.local -o targetUserName` ##### GitLab Enterprise `SCMKit.exe -s gitlab -m removeadmin -c userName:password -u https://gitlab.something.local -o targetUserName` `SCMKit.exe -s gitlab -m removeadmin -c apikey -u https://gitlab.something.local -o targetUserName` ##### Bitbucket Server Only username/password auth is supported to perform actions not related to repos or projects in Bitbucket. `SCMKit.exe -s bitbucket -m removeadmin -c userName:password -u https://bitbucket.something.local -o targetUserName` #### Example Output ``` C:\>SCMKit.exe -s gitlab -m removeadmin -c username:password -u https://gitlab.hogwarts.local -o hgranger ================================================== Module: removeadmin System: gitlab Auth Type: Username/Password Options: hgranger Target URL: https://gitlab.hogwarts.local Timestamp: 1/14/2022 9:20:12 PM ================================================== [+] SUCCESS: The hgranger user was successfully removed from the admin role. ``` ### Create Access Token #### Use Case > *Create an access token to be used in a particular SCM system* #### Syntax Provide the `createpat` module, along with any relevant authentication information and URL. Additionally, provide the target user you would like to create an access token for. ##### GitLab Enterprise This can only be performed as an administrator. You will provide the username that you would like to create a PAT for. `SCMKit.exe -s gitlab -m createpat -c userName:password -u https://gitlab.something.local -o targetUserName` `SCMKit.exe -s gitlab -m createpat -c apikey -u https://gitlab.something.local -o targetUserName` ##### Bitbucket Server Creates PAT for the current user authenticating as. In Bitbucket you cannot create a PAT for another user, even as an admin. Only username/password auth is supported to perform actions not related to repos or projects in Bitbucket. Take note of the PAT ID that is shown after being created. You will need this when you need to remove the PAT in the future. `SCMKit.exe -s bitbucket -m createpat -c userName:password -u https://bitbucket.something.local ` #### Example Output ``` C:\>SCMKit.exe -s gitlab -m createpat -c username:password -u https://gitlab.hogwarts.local -o hgranger ================================================== Module: createpat System: gitlab Auth Type: Username/Password Options: hgranger Target URL: https://gitlab.hogwarts.local Timestamp: 1/20/2022 1:51:23 PM ================================================== ID | Name | Token ----------------------------------------------------- 59 | SCMKIT-AaCND | R3ySx_8HUn6UQ_6onETx [+] SUCCESS: The hgranger user personal access token was successfully added. ``` ### List Access Tokens #### Use Case > *List access tokens for a user on a particular SCM system* #### Syntax Provide the `listpat` module, along with any relevant authentication information and URL. ##### GitLab Enterprise Only requires admin if you want to list another user's PAT's. A regular user can list their own PAT's. `SCMKit.exe -s gitlab -m listpat -c userName:password -u https://gitlab.something.local -o targetUser` `SCMKit.exe -s gitlab -m listpat -c apikey -u https://gitlab.something.local -o targetUser` ##### Bitbucket Server List access tokens for current user. Only username/password auth is supported to perform actions not related to repos or projects in Bitbucket. `SCMKit.exe -s bitbucket -m listpat -c userName:password -u https://bitbucket.something.local` List access tokens for another user (requires admin). Only username/password auth is supported to perform actions not related to repos or projects in Bitbucket. `SCMKit.exe -s bitbucket -m listpat -c userName:password -u https://bitbucket.something.local -o targetUser` #### Example Output ``` C:\>SCMKit.exe -s gitlab -m listpat -c username:password -u https://gitlab.hogwarts.local -o hgranger ================================================== Module: listpat System: gitlab Auth Type: Username/Password Options: hgranger Target URL: https://gitlab.hogwarts.local Timestamp: 1/20/2022 1:54:41 PM ================================================== ID | Name | Active? | Scopes ---------------------------------------------------------------------------------------------- 59 | SCMKIT-AaCND | True | api, read_repository, write_repository ``` ### Remove Access Token #### Use Case > *Remove an access token for a user in a particular SCM system* #### Syntax Provide the `removepat` module, along with any relevant authentication information and URL. Additionally, provide the target user PAT ID you would like to remove an access token for. ##### GitLab Enterprise Only requires admin if you want to remove another user's PAT. A regular user can remove their own PAT. You have to provide the PAT ID to remove. This ID was shown whenever you created the PAT and also when you listed the PAT. `SCMKit.exe -s gitlab -m removepat -c userName:password -u https://gitlab.something.local -o patID` `SCMKit.exe -s gitlab -m removepat -c apikey -u https://gitlab.something.local -o patID` ##### Bitbucket Server Only username/password auth is supported to perform actions not related to repos or projects in Bitbucket. You have to provide the PAT ID to remove. This ID was shown whenever you created the PAT. `SCMKit.exe -s bitbucket -m removepat -c userName:password -u https://bitbucket.something.local -o patID` #### Example Output ``` C:\>SCMKit.exe -s gitlab -m removepat -c apikey -u https://gitlab.hogwarts.local -o 58 ================================================== Module: removepat System: gitlab Auth Type: API Key Options: 59 Target URL: https://gitlab.hogwarts.local Timestamp: 1/20/2022 1:56:47 PM ================================================== [*] INFO: Revoking personal access token of ID: 59 [+] SUCCESS: The personal access token of ID 59 was successfully revoked. ``` ### Create SSH Key #### Use Case > *Create an SSH key to be used in a particular SCM system* #### Syntax Provide the `createsshkey` module, along with any relevant authentication information and URL. ##### GitHub Enterprise Creates SSH key for the current user authenticating as. `SCMKit.exe -s github -m createsshkey -c userName:password -u https://github.something.local -o "ssh public key"` `SCMKit.exe -s github -m createsshkey -c apiToken -u https://github.something.local -o "ssh public key"` ##### GitLab Enterprise Creates SSH key for the current user authenticating as. Take note of the SSH key ID that is shown after being created. You will need this when you need to remove the SSH key in the future. `SCMKit.exe -s gitlab -m createsshkey -c userName:password -u https://gitlab.something.local -o "ssh public key"` `SCMKit.exe -s gitlab -m createsshkey -c apiToken -u https://gitlab.something.local -o "ssh public key"` ##### Bitbucket Server Creates SSH key for the current user authenticating as. Only username/password auth is supported to perform actions not related to repos or projects in Bitbucket. Take note of the SSH key ID that is shown after being created. You will need this when you need to remove the SSH key in the future. `SCMKit.exe -s bitbucket -m createsshkey -c userName:password -u https://bitbucket.something.local -o "ssh public key"` #### Example Output ``` C:\>SCMKit.exe -s bitbucket -m createsshkey -c username:password -u https://bitbucket.hogwarts.local -o "ssh-rsa..." ================================================== Module: createsshkey System: bitbucket Auth Type: Username/Password Options: ssh-rsa ... Target URL: http://bitbucket.hogwarts.local:7990 Timestamp: 2/7/2022 1:02:31 PM ================================================== SSH Key ID ------------ 16 [+] SUCCESS: The hpotter user SSH key was successfully added. ``` ### List SSH Keys #### Use Case > *List SSH keys for a user on a particular SCM system* #### Syntax Provide the `listsshkey` module, along with any relevant authentication information and URL. ##### GitHub Enterprise List SSH keys for current user. This will include SSH key ID's, which is needed when you would want to remove an SSH key. `SCMKit.exe -s github -m listsshkey -c userName:password -u https://github.something.local` `SCMKit.exe -s github -m listsshkey -c apiToken -u https://github.something.local` ##### GitLab Enterprise List SSH keys for current user. `SCMKit.exe -s gitlab -m listsshkey -c userName:password -u https://gitlab.something.local` `SCMKit.exe -s gitlab -m listsshkey -c apiToken -u https://gitlab.something.local` ##### Bitbucket Server List SSH keys for current user. Only username/password auth is supported to perform actions not related to repos or projects in Bitbucket. `SCMKit.exe -s bitbucket -m listsshkey -c userName:password -u https://bitbucket.something.local` #### Example Output ``` C:\>SCMKit.exe -s gitlab -m listsshkey -u http://gitlab.hogwarts.local -c apiToken ================================================== Module: listsshkey System: gitlab Auth Type: API Key Options: Target URL: https://gitlab.hogwarts.local Timestamp: 2/7/2022 4:09:40 PM ================================================== SSH Key ID | SSH Key Value | Title --------------------------------------------------------------- 9 | .....p50edigBAF4lipVZkAM= | SCMKIT-RLzie 10 | .....vGJLPGHiTwIxW9i+xAs= | SCMKIT-muFGU ``` ### Remove SSH Key #### Use Case > *Remove an SSH key for a user in a particular SCM system* #### Syntax Provide the `removesshkey` module, along with any relevant authentication information and URL. Additionally, provide the target user SSH key ID to remove. ##### GitHub Enterprise You have to provide the SSH key ID to remove. This ID was shown whenever you list SSH keys. `SCMKit.exe -s github -m removesshkey -c userName:password -u https://github.something.local -o sshKeyID` `SCMKit.exe -s github -m removesshkey -c apiToken -u https://github.something.local -o sshKeyID` ##### GitLab Enterprise You have to provide the SSH key ID to remove. This ID was shown whenever you created the SSH key and is also shown when listing SSH keys. `SCMKit.exe -s gitlab -m removesshkey -c userName:password -u https://gitlab.something.local -o sshKeyID` `SCMKit.exe -s gitlab -m removesshkey -c apiToken -u https://gitlab.something.local -o sshKeyID` ##### Bitbucket Server Only username/password auth is supported to perform actions not related to repos or projects in Bitbucket. You have to provide the SSH key ID to remove. This ID was shown whenever you created the SSH key and is also shown when listing SSH keys. `SCMKit.exe -s bitbucket -m removesshkey -c userName:password -u https://bitbucket.something.local -o sshKeyID` #### Example Output ``` C:\>SCMKit.exe -s bitbucket -m removesshkey -u http://bitbucket.hogwarts.local:7990 -c username:password -o 16 ================================================== Module: removesshkey System: bitbucket Auth Type: Username/Password Options: 16 Target URL: http://bitbucket.hogwarts.local:7990 Timestamp: 2/7/2022 1:48:03 PM ================================================== [+] SUCCESS: The SSH key of ID 16 was successfully revoked. ``` ### List Admin Stats #### Use Case > *List admin stats in GitHub Enterprise* #### Syntax Provide the `adminstats` module, along with any relevant authentication information and URL. Site admin access in GitHub Enterprise is required to use this module ##### GitHub Enterprise `SCMKit.exe -s github -m adminstats -c userName:password -u https://github.something.local` `SCMKit.exe -s github -m adminstats -c apikey -u https://github.something.local` #### Example Output ``` C:\>SCMKit.exe -s github -m adminstats -c username:password -u https://github-enterprise.hogwarts.local ================================================== Module: adminstats System: github Auth Type: Username/Password Options: Target URL: https://github-enterprise.hogwarts.local Timestamp: 1/14/2022 9:45:50 PM ================================================== Admin Users | Suspended Users | Total Users ------------------------------------------------------ 1 | 0 | 5 Total Repos | Total Wikis ----------------------------------- 4 | 0 Total Orgs | Total Team Members | Total Teams ---------------------------------------------------------- 1 | 0 | 0 Private Gists | Public Gists ----------------------------------- 0 | 1 ``` ### List Branch Protection #### Use Case > *List branch protections in GitHub Enterprise* #### Syntax Provide the `protection` module, along with any relevant authentication information and URL. Optionally, supply a string in the options parameter to return matching results contained in repo names ##### GitHub Enterprise `SCMKit.exe -s github -m protection -c userName:password -u https://github.something.local` `SCMKit.exe -s github -m protection -c apikey -u https://github.something.local` `SCMKit.exe -s github -m protection -c apikey -u https://github.something.local -o reponame` #### Example Output ``` C:\>.\SCMKit.exe -u http://github.hogwarts.local -s github -c apiToken -m protection -o public-r ================================================== Module: protection System: github Auth Type: API Key Options: public-r Target URL: http://github.hogwarts.local Timestamp: 8/29/2022 2:02:42 PM ================================================== Repo | Branch | Protection ---------------------------------------------------------------------------------------------------------- public-repo | dev | Protected: True Status checks must pass before merge: Branch must be up-to-date before merge: True Owner review required before merge: True Approvals required before merge: 2 Protections apply to repo admins: True public-repo | main | Protected: False ``` ## Detection Below are static signatures for the specific usage of this tool in its default state: * Project GUID - `{266C644A-69B1-426B-A47C-1CF32B211F80}` * See [SCMKit Yara Rule](Detections/SCMKit.yar) in this repo. * User Agent String - `SCMKIT-5dc493ada400c79dd318abbe770dac7c` * See [SCMKit Snort Rule](Detections/SCMKit.rules) in this repo. * Access Token & SSH Key Names - Access tokens and SSH keys that are created using the tool are prepended with `SCMKIT-` for the name. For detection guidance of the techniques used by the tool, see the X-Force Red [blog post](https://securityintelligence.com/posts/abusing-source-code-management-systems). ## References * Bitbucket API Documentation * https://developer.atlassian.com/server/bitbucket/reference/rest-api/ * Octokit Documentation * https://octokitnet.readthedocs.io/en/latest/ * https://github.com/octokit/octokit.net * GitHub API Documentation * https://docs.github.com/en/rest/overview * GitLab API Documentation * https://docs.gitlab.com/ee/api/api_resources.html * GitLabApiClient Nuget Package Documentation * https://github.com/nmklotas/GitLabApiClient ================================================ FILE: SCMKit/App.config ================================================  ================================================ FILE: SCMKit/FodyWeavers.xml ================================================  ================================================ FILE: SCMKit/Properties/AssemblyInfo.cs ================================================ using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyTitle("SCMKit")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("SCMKit")] [assembly: AssemblyCopyright("Copyright © 2022")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. [assembly: ComVisible(false)] // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("266c644a-69b1-426b-a47c-1cf32b211f80")] // Version information for an assembly consists of the following four values: // // Major Version // Minor Version // Build Number // Revision // // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("1.2")] [assembly: AssemblyFileVersion("1.2")] ================================================ FILE: SCMKit/SCMKit.cs ================================================ using System; using System.Collections.Generic; using System.Threading.Tasks; namespace SCMKit { class SCMKit { // variables to be used private static string module = ""; private static string credential = ""; private static string system = ""; private static string url = ""; private static string option = ""; private static List approvedModules = new List { "listrepo", "repolist", "reposearch", "codesearch", "snippetlist", "gistlist", "orglist", "searchrepo", "searchcode", "searchfile", "listsnippet", "listgist", "listorg", "privs", "addadmin", "removeadmin", "createpat", "removepat", "listpat", "adminstats", "listrunner", "runnerlist", "createsshkey", "removesshkey", "listsshkey", "protection" }; static async Task Main(string[] args) { try { Dictionary argDict = library.Utils.ParseTheArguments(args); // dictionary to hold arguments // if no arguments given, display help and return if ((args.Length > 0 && argDict.Count == 0) || argDict.ContainsKey("h") || argDict.ContainsKey("help")) { library.Utils.HelpMe(); return; } // if url is not set, display message and exit if ((!argDict.ContainsKey("u") && !argDict.ContainsKey("url"))) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Must supply a URL. See the README."); return; } // if system is not set, display message and exit if ((!argDict.ContainsKey("s") && !argDict.ContainsKey("system"))) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Must supply a system. See the README."); return; } // if both module and credential are not given, display message and exit if ((!argDict.ContainsKey("m") && !argDict.ContainsKey("module")) && (!argDict.ContainsKey("c") && !argDict.ContainsKey("credential"))) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Must supply both a module and credential. See the README."); return; } // initialize variables // module if (argDict.ContainsKey("m") || argDict.ContainsKey("module")) { if (argDict.ContainsKey("m")) { module = argDict["m"]; } else { module = argDict["module"]; } } // options if (argDict.ContainsKey("o") || argDict.ContainsKey("options")) { if (argDict.ContainsKey("o")) { option = argDict["o"]; } else { option = argDict["options"]; } } // credential if (argDict.ContainsKey("c") || argDict.ContainsKey("credential")) { if (argDict.ContainsKey("c")) { credential = argDict["c"]; } else { credential = argDict["credential"]; } } // url if (argDict.ContainsKey("u") || argDict.ContainsKey("url")) { if (argDict.ContainsKey("u")) { url = argDict["u"]; } else { url = argDict["url"]; } } // system if (argDict.ContainsKey("s") || argDict.ContainsKey("system")) { if (argDict.ContainsKey("s")) { system = argDict["s"]; } else { system = argDict["system"]; } } // determine if invalid module was given if (!approvedModules.Contains(module.ToLower())) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Invalid module given. Please see the README for approved modules."); return; } // system is github if (system.ToLower().Equals("github")) { // get to the appropriate module that user specified switch (module.ToLower()) { case "listrepo": await modules.github.RepoList.execute(credential, url, option, system); break; case "searchrepo": await modules.github.RepoSearch.execute(credential, url, option, system); break; case "searchcode": await modules.github.CodeSearch.execute(credential, url, option, system); break; case "searchfile": await modules.github.FileSearch.execute(credential, url, option, system); break; case "listgist": await modules.github.GistList.execute(credential, url, option, system); break; case "listorg": await modules.github.OrgList.execute(credential, url, option, system); break; case "repolist": await modules.github.RepoList.execute(credential, url, option, system); break; case "reposearch": await modules.github.RepoSearch.execute(credential, url, option, system); break; case "codesearch": await modules.github.CodeSearch.execute(credential, url, option, system); break; case "filesearch": await modules.github.FileSearch.execute(credential, url, option, system); break; case "gistlist": await modules.github.GistList.execute(credential, url, option, system); break; case "orglist": await modules.github.OrgList.execute(credential, url, option, system); break; case "privs": await modules.github.Privs.execute(credential, url, option, system); break; case "addadmin": await modules.github.AddAdmin.execute(credential, url, option, system); break; case "removeadmin": await modules.github.RemoveAdmin.execute(credential, url, option, system); break; case "adminstats": await modules.github.AdminStats.execute(credential, url, option, system); break; case "createsshkey": await modules.github.CreateSSHKey.execute(credential, url, option, system); break; case "listsshkey": await modules.github.ListSSHKeys.execute(credential, url, option, system); break; case "removesshkey": await modules.github.RemoveSSHKey.execute(credential, url, option, system); break; case "protection": await modules.github.BranchProtection.execute(credential, url, option, system); break; default: Console.WriteLine(""); Console.WriteLine("[-] ERROR: That module is not supported for " + system + ". Please see README"); Console.WriteLine(""); Environment.Exit(1); break; } } // system is gitlab else if (system.ToLower().Equals("gitlab")) { // get to the appropriate module that user specified switch (module.ToLower()) { case "listrepo": await modules.gitlab.RepoList.execute(credential, url, option, system); break; case "searchrepo": await modules.gitlab.RepoSearch.execute(credential, url, option, system); break; case "searchcode": await modules.gitlab.CodeSearch.execute(credential, url, option, system); break; case "searchfile": await modules.gitlab.FileSearch.execute(credential, url, option, system); break; case "listsnippet": await modules.gitlab.SnippetList.execute(credential, url, option, system); break; case "repolist": await modules.gitlab.RepoList.execute(credential, url, option, system); break; case "reposearch": await modules.gitlab.RepoSearch.execute(credential, url, option, system); break; case "codesearch": await modules.gitlab.CodeSearch.execute(credential, url, option, system); break; case "filesearch": await modules.gitlab.FileSearch.execute(credential, url, option, system); break; case "snippetlist": await modules.gitlab.SnippetList.execute(credential, url, option, system); break; case "privs": await modules.gitlab.Privs.execute(credential, url, option, system); break; case "createpat": await modules.gitlab.CreatePAT.execute(credential, url, option, system); break; case "listpat": await modules.gitlab.ListPAT.execute(credential, url, option, system); break; case "removepat": await modules.gitlab.RemovePAT.execute(credential, url, option, system); break; case "addadmin": await modules.gitlab.AddAdmin.execute(credential, url, option, system); break; case "removeadmin": await modules.gitlab.RemoveAdmin.execute(credential, url, option, system); break; case "listrunner": await modules.gitlab.RunnerList.execute(credential, url, option, system); break; case "runnerlist": await modules.gitlab.RunnerList.execute(credential, url, option, system); break; case "createsshkey": await modules.gitlab.CreateSSHKey.execute(credential, url, option, system); break; case "removesshkey": await modules.gitlab.RemoveSSHKey.execute(credential, url, option, system); break; case "listsshkey": await modules.gitlab.ListSSHKeys.execute(credential, url, option, system); break; default: Console.WriteLine(""); Console.WriteLine("[-] ERROR: That module is not supported for " + system + ". Please see README"); Console.WriteLine(""); Environment.Exit(1); break; } } // system is bitbucket else if (system.ToLower().Equals("bitbucket")) { // get to the appropriate module that user specified switch (module.ToLower()) { case "listrepo": await modules.bitbucket.RepoList.execute(credential, url, option, system); break; case "searchrepo": await modules.bitbucket.RepoSearch.execute(credential, url, option, system); break; case "searchcode": await modules.bitbucket.CodeSearch.execute(credential, url, option, system); break; case "searchfile": await modules.bitbucket.FileSearch.execute(credential, url, option, system); break; case "repolist": await modules.bitbucket.RepoList.execute(credential, url, option, system); break; case "reposearch": await modules.bitbucket.RepoSearch.execute(credential, url, option, system); break; case "codesearch": await modules.bitbucket.CodeSearch.execute(credential, url, option, system); break; case "filesearch": await modules.bitbucket.FileSearch.execute(credential, url, option, system); break; case "createpat": await modules.bitbucket.CreatePAT.execute(credential, url, option, system); break; case "listpat": await modules.bitbucket.ListPAT.execute(credential, url, option, system); break; case "removepat": await modules.bitbucket.RemovePAT.execute(credential, url, option, system); break; case "addadmin": await modules.bitbucket.AddAdmin.execute(credential, url, option, system); break; case "removeadmin": await modules.bitbucket.RemoveAdmin.execute(credential, url, option, system); break; case "createsshkey": await modules.bitbucket.CreateSSHKey.execute(credential, url, option, system); break; case "removesshkey": await modules.bitbucket.RemoveSSHKey.execute(credential, url, option, system); break; case "listsshkey": await modules.bitbucket.ListSSHKeys.execute(credential, url, option, system); break; default: Console.WriteLine(""); Console.WriteLine("[-] ERROR: That module is not supported for " + system + ". Please see README"); Console.WriteLine(""); break; } } // invalid system given else { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Invalid system given. Please see the README for approved modules."); return; } } // end try catch (Exception ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR : {0}", ex.Message); } } // end main } // end class } // end namespace ================================================ FILE: SCMKit/SCMKit.csproj ================================================  Debug AnyCPU {266C644A-69B1-426B-A47C-1CF32B211F80} Exe SCMKit SCMKit v4.8 512 true true publish\ true Disk false Foreground 7 Days false false true 0 1.0.0.%2a false false true AnyCPU true full false bin\Debug\ DEBUG;TRACE prompt 4 AnyCPU none true bin\Release\ TRACE prompt 4 ..\packages\Costura.Fody.3.3.3\lib\net40\Costura.dll ..\packages\GitLabApiClient.1.8.0\lib\net48\GitLabApiClient.dll ..\packages\Newtonsoft.Json.13.0.1\lib\net45\Newtonsoft.Json.dll ..\packages\Octokit.2.0.1\lib\netstandard2.0\Octokit.dll False Microsoft .NET Framework 4.8 %28x86 and x64%29 true False .NET Framework 3.5 SP1 false This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. ================================================ FILE: SCMKit/library/BitbucketUtils.cs ================================================ using System; using System.IO; using System.Net; using System.Net.Security; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; using Newtonsoft.Json; namespace SCMKit.library { class BitbucketUtils { /** * authenticate to Bitbucket to get SESSION ID if not using API auth * * */ public static string GetSessionID(string credentials, string url) { try { ServicePointManager.ServerCertificateValidationCallback = delegate (object s, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { return true; }; string sessID = ""; bool authValid = false; var webRequest = (HttpWebRequest)System.Net.WebRequest.Create(url + "/j_atl_security_check"); if (webRequest != null) { // set header values webRequest.Method = "POST"; webRequest.ContentType = "application/x-www-form-urlencoded"; webRequest.UserAgent = "SCMKIT-5dc493ada400c79dd318abbe770dac7c"; webRequest.AllowAutoRedirect = false; string[] theCreds = credentials.Split(':'); // set body and send request using (var streamWriter = new StreamWriter(webRequest.GetRequestStream())) { string body = "j_username=" + theCreds[0] + "&j_password=" + theCreds[1] + "&_atl_remember_me=on&submit=Log+in"; streamWriter.Write(body); } // get the response and the Bitbucket session ID var httpResponse = (HttpWebResponse)webRequest.GetResponse(); var streamReader = new StreamReader(httpResponse.GetResponseStream()); string result = streamReader.ReadToEnd(); for (int i = 0; i < httpResponse.Headers.Count; ++i) { string[] splitValues = httpResponse.Headers[i].Split(','); foreach (string val in splitValues) { // this header is set only if auth request is valid if (val.Contains("_atl_bitbucket_remember_me")) { authValid = true; } // get the bitbucket session ID if (val.Contains("BITBUCKETSESSIONID=") && authValid) { string[] splitValsAgain = val.Split(';'); sessID = splitValsAgain[0]; sessID = sessID.Replace("BITBUCKETSESSIONID=", ""); } } } } // if auth was valid return the session ID if (authValid) { return sessID; } // if auth wasn't valid, return error and exit else { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Could not authenticate to URL with credentials provided."); Console.WriteLine(""); return null; } } catch (Exception ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Could not authenticate to URL with credentials provided. Exception: " + ex.ToString()); Console.WriteLine(""); return null; } } /** * Get the full Bitbucket URL, based on project key and repo slug * * */ public static async Task GetFullBitbucketRepoURLAsync(string credential, string sessID, string projKey, string repoSlug, string url) { string urlToReturn = "Unable to Determine"; try { ServicePointManager.ServerCertificateValidationCallback = delegate (object s, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { return true; }; ServicePointManager.Expect100Continue = true; ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; // web request to get a repo details in Bitbucket via API var webRequest = (HttpWebRequest)System.Net.WebRequest.Create(url + "/rest/api/1.0/projects/" + projKey + "/repos/" + repoSlug + ""); if (webRequest != null) { // set header values webRequest.Method = "GET"; webRequest.ContentType = "application/json"; webRequest.UserAgent = "SCMKIT-5dc493ada400c79dd318abbe770dac7c"; // if username/password auth was used, then pass the sessionID if (credential.Contains(":")) { webRequest.Headers.Add("Cookie", "BITBUCKETSESSIONID= " + sessID); } // if user just specified http access token else { webRequest.Headers.Add("Authorization", "Bearer " + credential); } // get web response WebResponse myWebResponse = await webRequest.GetResponseAsync(); string content; var reader = new StreamReader(myWebResponse.GetResponseStream()); content = reader.ReadToEnd(); // parse the JSON output and display results JsonTextReader jsonResult = new JsonTextReader(new StringReader(content)); string propName = ""; // read the json results while (jsonResult.Read()) { switch (jsonResult.TokenType.ToString()) { case "PropertyName": propName = jsonResult.Value.ToString(); break; case "String": if (propName.ToLower().Equals("href") && jsonResult.Value.ToString().ToLower().Contains(repoSlug) && jsonResult.Value.ToString().EndsWith(".git") && jsonResult.Value.ToString().StartsWith("http")) { urlToReturn = jsonResult.Value.ToString(); urlToReturn = urlToReturn.Replace(".git", ""); } break; default: break; } } } } catch (Exception ex) { return urlToReturn; } return urlToReturn; } } } ================================================ FILE: SCMKit/library/GitHubUtils.cs ================================================ using System; using System.IO; using System.Net; using System.Net.Security; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; using Octokit; namespace SCMKit.library { class GitHubUtils { /** * authenticate to GitHub * * */ public static dynamic AuthToGitHub(string credentials, string url) { try { ServicePointManager.ServerCertificateValidationCallback = delegate (object s, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { return true; }; GitHubClient client = null; Credentials auth = null; Uri uri = new Uri(url); client = new GitHubClient(new ProductHeaderValue("SCMKIT-5dc493ada400c79dd318abbe770dac7c"), uri); // if username/password auth being used if (credentials.Contains(":")) { string[] theCreds = credentials.Split(':'); auth = new Credentials(theCreds[0], theCreds[1]); } // if token auth being used else { auth = new Credentials(credentials); } client.Credentials = auth; return client; } catch (Exception ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Could not authenticate to URL with credentials provided. Exception: " + ex.ToString()); Console.WriteLine(""); Environment.Exit(1); return null; } } public static async Task callGitHubApiGet(string credential, string baseUrl, string path, string query) { string content = ""; var webRequest = (HttpWebRequest)WebRequest.Create(baseUrl + path + query); // set header values webRequest.Method = "GET"; webRequest.ContentType = "application/json"; webRequest.UserAgent = "SCMKIT-5dc493ada400c79dd318abbe770dac7c"; // if we need to pass the username/password if (credential.Contains(":")) { var base64EncodedAuthenticationString = Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes(credential)); webRequest.Headers.Add("Authorization", "Basic " + base64EncodedAuthenticationString); } // if user just specified personal access token else { webRequest.Headers.Add("Authorization", "Bearer " + credential); } // get web response WebResponse response = await webRequest.GetResponseAsync(); var reader = new StreamReader(response.GetResponseStream()); content = reader.ReadToEnd(); return content; } } } ================================================ FILE: SCMKit/library/GitLabUtils.cs ================================================ using System; using System.IO; using System.Net; using System.Net.Security; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; using GitLabApiClient; namespace SCMKit.library { class GitLabUtils { /** * Get the GitLab project visibilty based on project ID * * */ public static async Task GetGitLabProjectVisibility(string credential, string accessToken, string projID, string url) { string visibility = "public"; try { ServicePointManager.ServerCertificateValidationCallback = delegate (object s, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { return true; }; ServicePointManager.Expect100Continue = true; ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; // web request to get details of a project var webRequest = (HttpWebRequest)System.Net.WebRequest.Create(url + "/api/v4/projects/" + projID); if (webRequest != null) { // set header values webRequest.Method = "GET"; webRequest.ContentType = "application/json"; webRequest.UserAgent = "SCMKIT-5dc493ada400c79dd318abbe770dac7c"; // if we need to pass the token obtained from the username/password auth if (credential.Contains(":")) { webRequest.Headers.Add("Authorization", "Bearer " + accessToken); } // if user just specified personal access token else { webRequest.Headers.Add("PRIVATE-TOKEN", credential); } // get web response WebResponse myWebResponse = await webRequest.GetResponseAsync(); string content; var reader = new StreamReader(myWebResponse.GetResponseStream()); content = reader.ReadToEnd(); // parse response to get visibility int startingIndex = content.IndexOf("\"visibility\":"); int endingIndex = content.IndexOf("\"owner\":{"); visibility = content.Substring(startingIndex + "\"visibility\":".Length, endingIndex - startingIndex - "\"visibility\":".Length); visibility = visibility.Replace("\"", ""); visibility = visibility.Replace(",", ""); } } catch (Exception ex) { return visibility; } return visibility; } /** * Authenticate to GitLab * * */ public static async Task AuthToGitLabAsync(string credentials, string url) { try { ServicePointManager.ServerCertificateValidationCallback = delegate (object s, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { return true; }; GitLabClient client = null; Uri uri = new Uri(url); // if username/password auth being used if (credentials.Contains(":")) { string[] theCreds = credentials.Split(':'); client = new GitLabClient(uri.ToString()); await client.LoginAsync(theCreds[0], theCreds[1]); } // if token auth being used else { client = new GitLabClient(uri.ToString(), credentials); } return client; } catch (Exception ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Could not authenticate to URL with credentials provided. Exception: " + ex.ToString()); Console.WriteLine(""); return null; } } /** * get access token for GitLab based on username/password given * * */ public static string GetAccessToken(string credential, string url) { string accessToken = ""; // get personal access tokens for user var webRequestOAuthToken = System.Net.WebRequest.Create(url + "/oauth/token"); if (webRequestOAuthToken != null) { // set header values webRequestOAuthToken.Method = "POST"; webRequestOAuthToken.ContentType = "application/json"; // set body and sent request using (var streamWriter = new StreamWriter(webRequestOAuthToken.GetRequestStream())) { string[] theCreds = credential.Split(':'); string json = "{\"grant_type\":\"password\",\"scope\":\"api\",\"username\":\"" + theCreds[0] + "\",\"password\":\"" + theCreds[1] + "\"}"; streamWriter.Write(json); } // get the response and the access token var httpResponse = (HttpWebResponse)webRequestOAuthToken.GetResponse(); var streamReader = new StreamReader(httpResponse.GetResponseStream()); string result = streamReader.ReadToEnd(); int startIndex = result.IndexOf("\"access_token\":\""); int endIndex = result.IndexOf("\",\"token_type\":"); accessToken = result.Substring(startIndex + "\"access_token\":\"".Length, endIndex - startIndex - "\", \"token_type\":".Length); } return accessToken; } } } ================================================ FILE: SCMKit/library/Utils.cs ================================================ using System; using System.Collections.Generic; using System.Net; using System.Net.Security; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; namespace SCMKit.library { class Utils { /** * Parse command line arguments * * */ public static Dictionary ParseTheArguments(string[] args) { try { Dictionary ret = new Dictionary(); if (args.Length % 2 == 0 && args.Length > 0) { for (int i = 0; i < args.Length; i = i + 2) { ret.Add(args[i].Substring(1, args[i].Length - 1).ToLower(), args[i + 1]); } } return ret; } catch (ArgumentException ex) { Console.WriteLine(""); Console.WriteLine("[-] You specified duplicate switches. Check your command again. Exception: " + ex.ToString()); return null; } } // end /** * print help * * */ public static void HelpMe() { Console.Write("\nPlease read the README page for proper usage of the tool.\n\n"); } // end print help method /** * Generate module header * * */ public static string GenerateHeader(string module, string credential, string url, string options, string system) { string output = String.Empty; string delim = "=================================================="; string authType = ""; if (credential.Contains(":")) { authType = "Username/Password"; } else { authType = "API Key"; } output += "\n" + delim + "\n"; output += "Module:\t\t" + module + "\n"; output += "System:\t\t" + system + "\n"; output += "Auth Type:\t" + authType + "\n"; output += "Options:\t" + options + "\n"; output += "Target URL:\t" + url + "\n\n"; output += "Timestamp:\t" + DateTime.Now + "\n"; output += delim + "\n"; return output; } /** * Heartbeat request to indicate it is SCMKit being used for modules not using raw HTTP requests * */ public static async Task HeartbeatRequest(string url) { ServicePointManager.ServerCertificateValidationCallback = delegate (object s, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { return true; }; ServicePointManager.Expect100Continue = true; ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; WebResponse myWebResponse = null; try { var webRequest = (HttpWebRequest)System.Net.WebRequest.Create(url); if (webRequest != null) { // set header values webRequest.Method = "GET"; webRequest.ContentType = "application/json"; webRequest.UserAgent = "SCMKIT-5dc493ada400c79dd318abbe770dac7c"; // get web response myWebResponse = await webRequest.GetResponseAsync(); } } catch (Exception ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Could not perform heartbeat request. Exception: " + ex.ToString()); Console.WriteLine(""); Environment.Exit(1); } return myWebResponse; } /** * method to get all indexes where a value exists in a string * * */ public static int[] AllIndexesOf(string str, string substr, bool ignoreCase = false) { if (string.IsNullOrWhiteSpace(str) || string.IsNullOrWhiteSpace(substr)) { throw new ArgumentException("String or substring is not specified."); } var indexes = new List(); int index = 0; while ((index = str.IndexOf(substr, index, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal)) != -1) { indexes.Add(index++); } return indexes.ToArray(); } } } ================================================ FILE: SCMKit/library/WebUtils.cs ================================================ using System; using System.IO; using System.Net; using System.Net.Security; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; namespace SCMKit.library { class WebUtils { /** * generate web request * * */ public static HttpWebRequest GenerateWebRequest(string credential, string url, string method) { // Have to use HttpWebRequest class in order to set the Accept property. // Can't set the Accept header directly - C# throws an error. HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url); request.Method = method; request.ContentType = "application/json"; request.Accept = "application/vnd.github.v3+json"; request.UserAgent = "SCMKIT-5dc493ada400c79dd318abbe770dac7c"; if (credential.Contains(":")) { request.Headers.Add("Authorization", "Basic " + Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(credential))); } else { request.Headers.Add("Authorization", "token " + credential); } return request; } /** * generate raw file web request * * */ public static HttpWebRequest GenerateRawFileWebRequest(string credential, string url) { // Have to use HttpWebRequest class in order to set the Accept property. // Can't set the Accept header directly - C# throws an error. HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url); request.Method = "GET"; request.Accept = "application/vnd.github.v3.raw+raw"; request.UserAgent = "SCMKIT-5dc493ada400c79dd318abbe770dac7c"; if (credential.Contains(":")) { request.Headers.Add("Authorization", "Basic " + Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(credential))); } else { request.Headers.Add("Authorization", "token " + credential); } return request; } /** * get web response string * * */ public static async Task GetRequestResponseString(HttpWebRequest request) { var responseAsync = await request.GetResponseAsync(); var responseFromServer = ""; using (var dataStream = responseAsync.GetResponseStream()) { // Open the stream using a StreamReader for easy access. var reader = new StreamReader(dataStream); // Read the content. responseFromServer = reader.ReadToEnd(); } // Close the response. responseAsync.Close(); return responseFromServer; } /** * get web response * * */ public static async Task GetRequestResponse(HttpWebRequest request) { var responseAsync = await request.GetResponseAsync(); return responseAsync; } /** * ignore SSL * * */ public static void IgnoreSSL() { ServicePointManager.ServerCertificateValidationCallback = (object s, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) => true; ServicePointManager.Expect100Continue = true; ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; } } } ================================================ FILE: SCMKit/modules/bitbucket/AddAdmin.cs ================================================ using System; using System.Net; using System.Threading.Tasks; using System.IO; using System.Security.Cryptography.X509Certificates; using System.Net.Security; namespace SCMKit.modules.bitbucket { class AddAdmin { public static async Task execute(string credential, string url, string options, string system) { // Generate module header Console.WriteLine(library.Utils.GenerateHeader("addadmin", credential, url, options, system)); try { string sessID = ""; // if username/password auth is used, get session ID for remainder of requests if (credential.Contains(":")) { sessID = library.BitbucketUtils.GetSessionID(credential, url); if (sessID == null) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Credentials provided are not valid."); Console.WriteLine(""); return; } } // if API token was provided, display message and return else { Console.WriteLine(""); Console.WriteLine("[-] ERROR: API token authentication is not supported for this module. Please provide username/password with the appropriate permissions."); Console.WriteLine(""); return; } ServicePointManager.ServerCertificateValidationCallback = delegate (object s, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { return true; }; ServicePointManager.Expect100Continue = true; ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; await library.Utils.HeartbeatRequest(url); // web request to add admin via rest API var webRequest = (HttpWebRequest)System.Net.WebRequest.Create(url + "/rest/api/1.0/admin/permissions/users?name=" + options + "&permission=ADMIN"); if (webRequest != null) { // set header values webRequest.Method = "PUT"; webRequest.ContentType = "application/json"; webRequest.UserAgent = "SCMKIT-5dc493ada400c79dd318abbe770dac7c"; // if username/password auth was used, then pass the sessionID if (credential.Contains(":")) { webRequest.Headers.Add("Cookie", "BITBUCKETSESSIONID= " + sessID); } // if user just specified http access token else { webRequest.Headers.Add("Authorization", "Bearer " + credential); } // get web response WebResponse myWebResponse = await webRequest.GetResponseAsync(); string content; var reader = new StreamReader(myWebResponse.GetResponseStream()); content = reader.ReadToEnd(); bool addAdminSuccessful = false; // figure out if request was successful for (int i = 0; i < myWebResponse.Headers.Count; ++i) { if (myWebResponse.Headers.Keys[i].ToString().ToLower().Equals("x-auserid")) { addAdminSuccessful = true; } } if (addAdminSuccessful) { Console.WriteLine(""); Console.WriteLine("[+] SUCCESS: Successfully added " + options + " user to the admin role."); Console.WriteLine(""); } // if not successful else { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Credentials (username/password OR API token) provided are not valid."); Console.WriteLine(""); } } } catch (Exception ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Could not add user to admin group. Exception: " + ex.ToString()); Console.WriteLine(""); } } } } ================================================ FILE: SCMKit/modules/bitbucket/CodeSearch.cs ================================================ using System; using System.Collections.Generic; using System.Net; using System.Threading.Tasks; using System.IO; using Newtonsoft.Json; using System.Security.Cryptography.X509Certificates; using System.Net.Security; namespace SCMKit.modules.bitbucket { // custom class to handle URL results public class URLResult { public URLResult(string link, string matchingLine, string fileName) { this.url = link; this.matchingLine = matchingLine; this.fileName = fileName; } public string url { get; set; } public string matchingLine { get; set; } public string fileName { get; set; } } // end URLResult class class CodeSearch { // dictionary to hold the list of repos and their URLs private static List urlResults = new List(); private static int matchCount = 0; public static async Task execute(string credential, string url, string options, string system) { // Generate module header Console.WriteLine(library.Utils.GenerateHeader("searchcode", credential, url, options, system)); try { string sessID = ""; // if username/password auth is used, get session ID for remainder of requests if (credential.Contains(":")) { sessID = library.BitbucketUtils.GetSessionID(credential, url); if (sessID == null) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Credentials provided are not valid."); Console.WriteLine(""); return; } } ServicePointManager.ServerCertificateValidationCallback = delegate (object s, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { return true; }; ServicePointManager.Expect100Continue = true; ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; await library.Utils.HeartbeatRequest(url); // web request to search via rest API var webRequest = (HttpWebRequest)System.Net.WebRequest.Create(url + "/rest/search/latest/search"); if (webRequest != null) { // set header values webRequest.Method = "POST"; webRequest.ContentType = "application/json"; webRequest.UserAgent = "SCMKIT-5dc493ada400c79dd318abbe770dac7c"; // if username/password auth was used, then pass the sessionID if (credential.Contains(":")) { webRequest.Headers.Add("Cookie", "BITBUCKETSESSIONID= " + sessID); } // if user just specified http access token else { webRequest.Headers.Add("Authorization", "Bearer " + credential); } // set body and send request using (var streamWriter = new StreamWriter(webRequest.GetRequestStream())) { string json = "{\"query\":\"" + options + "\",\"entities\":{\"code\":{}},\"limits\":{\"primary\":100,\"secondary\":100}}"; streamWriter.Write(json); } // get web response WebResponse myWebResponse = await webRequest.GetResponseAsync(); string content; var reader = new StreamReader(myWebResponse.GetResponseStream()); content = reader.ReadToEnd(); bool validCreds = false; // figure out if creds valid for (int i = 0; i < myWebResponse.Headers.Count; ++i) { if (myWebResponse.Headers.Keys[i].ToString().ToLower().Equals("x-auserid")) { validCreds = true; } } if (validCreds) { // parse the JSON output and display results JsonTextReader jsonResult = new JsonTextReader(new StringReader(content)); string matchingLine = ""; string propName = ""; string projKey = ""; string repoName = ""; string fileName = ""; string nextPageStart = ""; string isLastPage = ""; // read the json results while (jsonResult.Read()) { switch (jsonResult.TokenType.ToString()) { case "StartObject": break; case "EndObject": break; case "StartArray": break; case "EndArray": // add the match to the list if (!matchingLine.Equals("") && !fileName.Equals("")) { bool alreadyExists = false; string fullURL = await library.BitbucketUtils.GetFullBitbucketRepoURLAsync(credential, sessID, projKey, repoName, url); URLResult singleResults = new URLResult(fullURL, matchingLine, fileName); // only add if not already found before foreach (URLResult item in urlResults) { if (item.fileName.Equals(fileName) && item.matchingLine.Equals(matchingLine) && item.url.Equals(fullURL)) { alreadyExists = true; } } if (!alreadyExists) { urlResults.Add(singleResults); } } break; case "PropertyName": propName = jsonResult.Value.ToString(); break; case "String": // get actual values if (propName.ToLower().Equals("text")) { string filteredResult = jsonResult.Value.ToString().ToLower(); filteredResult = filteredResult.Replace("", ""); filteredResult = filteredResult.Replace("", ""); if (filteredResult.Contains(options.ToLower())) { matchingLine = jsonResult.Value.ToString(); } } if (propName.ToLower().Equals("key")) { projKey = jsonResult.Value.ToString(); } if (propName.ToLower().Equals("slug")) { repoName = jsonResult.Value.ToString(); } if (propName.ToLower().Equals("file")) { fileName = jsonResult.Value.ToString(); } break; case "Integer": if (propName.ToLower().Equals("nextstart")) { nextPageStart = jsonResult.Value.ToString(); } break; case "Boolean": if (propName.ToLower().Equals("islastpage")) { isLastPage = jsonResult.Value.ToString(); } break; default: break; } } // iterate through the dictionary of matching lines and print them foreach (var item in urlResults) { string theMatch = item.matchingLine; theMatch = theMatch.Replace("", ""); theMatch = theMatch.Replace("", ""); theMatch = WebUtility.HtmlDecode(theMatch); Console.WriteLine("\n[>] REPO: " + item.url); Console.WriteLine(" [>] FILE: " + item.fileName); Console.WriteLine(" |_ " + theMatch); matchCount++; } // if there are more pages, then make subsequent requests if (!nextPageStart.Equals("") && isLastPage.Equals("False")) { await makeSubsequentRequestAsync(credential, url, options, sessID, nextPageStart); } Console.WriteLine(""); Console.WriteLine("Total matching results: " + matchCount); } // if creds invalid else { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Credentials (username/password OR API token) provided are not valid."); Console.WriteLine(""); } } } catch (Exception ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Could not perform code search. Exception: " + ex.ToString()); Console.WriteLine(""); } } // this is just placeholder for making more than 1 request due to paging of results. will have better global solution at some point than this band-aid public static async Task makeSubsequentRequestAsync(string credential, string url, string options, string sessID, string nextPage) { try { urlResults.Clear(); ServicePointManager.ServerCertificateValidationCallback = delegate (object s, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { return true; }; ServicePointManager.Expect100Continue = true; ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; // web request to search via rest API var webRequest = (HttpWebRequest)System.Net.WebRequest.Create(url + "/rest/search/latest/search"); if (webRequest != null) { // set header values webRequest.Method = "POST"; webRequest.ContentType = "application/json"; webRequest.UserAgent = "SCMKIT-5dc493ada400c79dd318abbe770dac7c"; // if username/password auth was used, then pass the sessionID if (credential.Contains(":")) { webRequest.Headers.Add("Cookie", "BITBUCKETSESSIONID= " + sessID); } // if user just specified http access token else { webRequest.Headers.Add("Authorization", "Bearer " + credential); } // set body and send request using (var streamWriter = new StreamWriter(webRequest.GetRequestStream())) { string json = "{\"query\":\"" + options + "\",\"entities\":{\"code\":{\"start\":" + nextPage + "}},\"limits\":{\"primary\":100,\"secondary\":100}}"; streamWriter.Write(json); } // get web response WebResponse myWebResponse = await webRequest.GetResponseAsync(); string content; var reader = new StreamReader(myWebResponse.GetResponseStream()); content = reader.ReadToEnd(); bool validCreds = false; // figure out if creds valid for (int i = 0; i < myWebResponse.Headers.Count; ++i) { if (myWebResponse.Headers.Keys[i].ToString().ToLower().Equals("x-auserid")) { validCreds = true; } } if (validCreds) { // parse the JSON output and display results JsonTextReader jsonResult = new JsonTextReader(new StringReader(content)); string matchingLine = ""; string propName = ""; string projKey = ""; string repoName = ""; string fileName = ""; string nextPageStart = ""; string isLastPage = ""; // read the json results while (jsonResult.Read()) { switch (jsonResult.TokenType.ToString()) { case "StartObject": break; case "EndObject": break; case "StartArray": break; case "EndArray": // add the match to the list if (!matchingLine.Equals("") && !fileName.Equals("")) { bool alreadyExists = false; string fullURL = await library.BitbucketUtils.GetFullBitbucketRepoURLAsync(credential, sessID, projKey, repoName, url); URLResult singleResults = new URLResult(fullURL, matchingLine, fileName); // only add if not already found before foreach (URLResult item in urlResults) { if (item.fileName.Equals(fileName) && item.matchingLine.Equals(matchingLine) && item.url.Equals(fullURL)) { alreadyExists = true; } } if (!alreadyExists) { urlResults.Add(singleResults); } } break; case "PropertyName": propName = jsonResult.Value.ToString(); break; case "String": // get actual values if (propName.ToLower().Equals("text")) { string filteredResult = jsonResult.Value.ToString().ToLower(); filteredResult = filteredResult.Replace("", ""); filteredResult = filteredResult.Replace("", ""); if (filteredResult.Contains(options.ToLower())) { matchingLine = jsonResult.Value.ToString(); } } if (propName.ToLower().Equals("key")) { projKey = jsonResult.Value.ToString(); } if (propName.ToLower().Equals("slug")) { repoName = jsonResult.Value.ToString(); } if (propName.ToLower().Equals("file")) { fileName = jsonResult.Value.ToString(); } break; case "Integer": if (propName.ToLower().Equals("nextstart")) { nextPageStart = jsonResult.Value.ToString(); } break; case "Boolean": if (propName.ToLower().Equals("islastpage")) { isLastPage = jsonResult.Value.ToString(); } break; default: break; } } // iterate through the dictionary of matching lines and print them foreach (var item in urlResults) { string theMatch = item.matchingLine; theMatch = theMatch.Replace("", ""); theMatch = theMatch.Replace("", ""); theMatch = WebUtility.HtmlDecode(theMatch); Console.WriteLine("\n[>] REPO: " + item.url); Console.WriteLine(" [>] FILE: " + item.fileName); Console.WriteLine(" |_ " + theMatch); matchCount++; } // if there are more pages, then make subsequent requests if (!nextPageStart.Equals("") && isLastPage.Equals("False")) { await makeSubsequentRequestAsync(credential, url, options, sessID, nextPageStart); } } // if creds invalid else { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Credentials (username/password OR API token) provided are not valid."); Console.WriteLine(""); } } } catch (Exception ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Could not perform code search. Exception: " + ex.ToString()); Console.WriteLine(""); } } } } ================================================ FILE: SCMKit/modules/bitbucket/CreatePAT.cs ================================================ using System; using System.Collections.Generic; using System.Net; using System.Threading.Tasks; using System.IO; using System.Security.Cryptography.X509Certificates; using System.Net.Security; namespace SCMKit.modules.bitbucket { class CreatePAT { // hashtable to store mapping of PAT and permissions private static Dictionary patMapping = new Dictionary(); public static async Task execute(string credential, string url, string options, string system) { // Generate module header Console.WriteLine(library.Utils.GenerateHeader("createpat", credential, url, options, system)); try { string sessID = ""; // if username/password auth is used, get session ID for remainder of requests if (credential.Contains(":")) { sessID = library.BitbucketUtils.GetSessionID(credential, url); if (sessID == null) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Credentials provided are not valid."); Console.WriteLine(""); return; } } // if API token was provided, display message and return else { Console.WriteLine(""); Console.WriteLine("[-] ERROR: API token authentication is not supported for this module. Please provide username/password with the appropriate permissions."); Console.WriteLine(""); return; } // create table header string tableHeader = string.Format("{0,25} | {1,15} | {2,50}", "ID", "Name", "Token"); Console.WriteLine(tableHeader); Console.WriteLine(new String('-', tableHeader.Length)); ServicePointManager.ServerCertificateValidationCallback = delegate (object s, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { return true; }; ServicePointManager.Expect100Continue = true; ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; await library.Utils.HeartbeatRequest(url); string[] splitCred = credential.Split(':'); // web request to create PAT via REST api var webRequest = (HttpWebRequest)System.Net.WebRequest.Create(url + "/rest/access-tokens/1.0/users/" + splitCred[0]); if (webRequest != null) { // set header values webRequest.Method = "PUT"; webRequest.ContentType = "application/json"; webRequest.UserAgent = "SCMKIT-5dc493ada400c79dd318abbe770dac7c"; // if username/password auth was used, then pass the sessionID if (credential.Contains(":")) { webRequest.Headers.Add("Cookie", "BITBUCKETSESSIONID= " + sessID); } // if user just specified http access token else { webRequest.Headers.Add("Authorization", "Bearer " + credential); } // set body and send request using (var streamWriter = new StreamWriter(webRequest.GetRequestStream())) { // create random token name Random rd = new Random(); const string allowedChars = "ABCDEFGHJKLMNOPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; char[] chars = new char[5]; for (int i = 0; i < 5; i++) { chars[i] = allowedChars[rd.Next(0, allowedChars.Length)]; } string personalAccessTokenName = new string(chars); personalAccessTokenName = "SCMKIT-" + personalAccessTokenName; string json = "{\"name\": \"" + personalAccessTokenName + "\",\"permissions\": [\"REPO_ADMIN\",\"PROJECT_ADMIN\"],\"expiryDays\": \"\"}"; streamWriter.Write(json); } // get web response WebResponse myWebResponse = await webRequest.GetResponseAsync(); string content; var reader = new StreamReader(myWebResponse.GetResponseStream()); content = reader.ReadToEnd(); bool validCreds = false; // figure out if creds valid for (int i = 0; i < myWebResponse.Headers.Count; ++i) { if (myWebResponse.Headers.Keys[i].ToString().ToLower().Equals("x-auserid")) { validCreds = true; } } if (validCreds) { // get the PAT name and id string patName = ""; int startIndex = content.IndexOf("\"id\":"); int endIndex = content.IndexOf("\"permissions\":"); patName = content.Substring(startIndex + "\"id\":".Length, endIndex - startIndex - "\"id\"".Length); patName = patName.Replace("\"", ""); patName = patName.Replace(",", ""); string[] patNameArray = patName.Split(':'); patMapping.Add(patNameArray[0].Replace("createdDate", ""), patNameArray[patNameArray.Length - 1]); // parse the actual token string tokenContent = ""; int startIndexContent = content.IndexOf("\"token\":"); int endIndexContent = content.Length - 1; tokenContent = content.Substring(startIndexContent + "\"token\":".Length, endIndexContent - startIndexContent - "\"token\":".Length); tokenContent = tokenContent.Replace("\"", ""); foreach (var item in patMapping) { Console.WriteLine("{0,25} | {1,15} | {2,50}", item.Key, item.Value, tokenContent); } Console.WriteLine(""); Console.WriteLine("[+] SUCCESS: The " + splitCred[0] + " user personal access token was successfully added."); Console.WriteLine(""); } // if creds invalid else { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Credentials (username/password OR API token) provided are not valid."); Console.WriteLine(""); } } } catch (Exception ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Could not create PAT. Exception: " + ex.ToString()); Console.WriteLine(""); } } } } ================================================ FILE: SCMKit/modules/bitbucket/CreateSSHKey.cs ================================================ using System; using System.Net; using System.Threading.Tasks; using System.IO; using System.Security.Cryptography.X509Certificates; using System.Net.Security; namespace SCMKit.modules.bitbucket { class CreateSSHKey { public static async Task execute(string credential, string url, string options, string system) { // Generate module header Console.WriteLine(library.Utils.GenerateHeader("createsshkey", credential, url, options, system)); try { string sessID = ""; // if username/password auth is used, get session ID for remainder of requests if (credential.Contains(":")) { sessID = library.BitbucketUtils.GetSessionID(credential, url); if (sessID == null) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Credentials provided are not valid."); Console.WriteLine(""); return; } } // if API token was provided, display message and return else { Console.WriteLine(""); Console.WriteLine("[-] ERROR: API token authentication is not supported for this module. Please provide username/password with the appropriate permissions."); Console.WriteLine(""); return; } // create table header string tableHeader = string.Format("{0,12}", "SSH Key ID"); Console.WriteLine(tableHeader); Console.WriteLine(new String('-', tableHeader.Length)); ServicePointManager.ServerCertificateValidationCallback = delegate (object s, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { return true; }; ServicePointManager.Expect100Continue = true; ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; await library.Utils.HeartbeatRequest(url); string[] splitCred = credential.Split(':'); // web request to add SSH key via REST API var webRequest = (HttpWebRequest)System.Net.WebRequest.Create(url + "/rest/ssh/1.0/keys"); if (webRequest != null) { // set header values webRequest.Method = "POST"; webRequest.ContentType = "application/json"; webRequest.UserAgent = "SCMKIT-5dc493ada400c79dd318abbe770dac7c"; // if username/password auth was used, then pass the sessionID if (credential.Contains(":")) { webRequest.Headers.Add("Cookie", "BITBUCKETSESSIONID= " + sessID); } // if user just specified http access token else { webRequest.Headers.Add("Authorization", "Bearer " + credential); } // set body and send request using (var streamWriter = new StreamWriter(webRequest.GetRequestStream())) { // create random SSH Key label Random rd = new Random(); const string allowedChars = "ABCDEFGHJKLMNOPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; char[] chars = new char[5]; for (int i = 0; i < 5; i++) { chars[i] = allowedChars[rd.Next(0, allowedChars.Length)]; } string sshKeyLabel = new string(chars); sshKeyLabel = "SCMKIT-" + sshKeyLabel; string json = "{\"" + "text" + "\": \"" + options + " " + sshKeyLabel + "\"}"; streamWriter.Write(json); } // get web response WebResponse myWebResponse = await webRequest.GetResponseAsync(); string content; var reader = new StreamReader(myWebResponse.GetResponseStream()); content = reader.ReadToEnd(); bool validCreds = false; // figure out if creds valid for (int i = 0; i < myWebResponse.Headers.Count; ++i) { if (myWebResponse.Headers.Keys[i].ToString().ToLower().Equals("x-auserid")) { validCreds = true; } } if (validCreds) { // get the SSH key ID string sshKeyID = ""; int startIndex = content.IndexOf("\"id\":"); int endIndex = content.IndexOf("\"text\":"); sshKeyID = content.Substring(startIndex + "\"id\":".Length, endIndex - startIndex - "\"id\"".Length); sshKeyID = sshKeyID.Replace("\"", ""); sshKeyID = sshKeyID.Replace(",", ""); Console.WriteLine("{0,12}", sshKeyID); Console.WriteLine(""); Console.WriteLine("[+] SUCCESS: The " + splitCred[0] + " user SSH key was successfully added."); Console.WriteLine(""); } // if creds invalid else { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Credentials (username/password OR API token) provided are not valid."); Console.WriteLine(""); } } } catch (Exception ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Could not create SSH Key. Exception: " + ex.ToString()); Console.WriteLine(""); } } } } ================================================ FILE: SCMKit/modules/bitbucket/FileSearch.cs ================================================ using System; using System.Collections.Generic; using System.Net; using System.Threading.Tasks; using System.IO; using Newtonsoft.Json; using System.Security.Cryptography.X509Certificates; using System.Net.Security; namespace SCMKit.modules.bitbucket { class FileSearch { // dictionary to hold the list of repos and their URLs private static List urlResults = new List(); private static int matchCount = 0; public static async Task execute(string credential, string url, string options, string system) { // Generate module header Console.WriteLine(library.Utils.GenerateHeader("searchfile", credential, url, options, system)); try { string sessID = ""; // if username/password auth is used, get session ID for remainder of requests if (credential.Contains(":")) { sessID = library.BitbucketUtils.GetSessionID(credential, url); if (sessID == null) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Credentials provided are not valid."); Console.WriteLine(""); return; } } ServicePointManager.ServerCertificateValidationCallback = delegate (object s, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { return true; }; ServicePointManager.Expect100Continue = true; ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; await library.Utils.HeartbeatRequest(url); // web request to search via rest API var webRequest = (HttpWebRequest)System.Net.WebRequest.Create(url + "/rest/search/latest/search"); if (webRequest != null) { // set header values webRequest.Method = "POST"; webRequest.ContentType = "application/json"; webRequest.UserAgent = "SCMKIT-5dc493ada400c79dd318abbe770dac7c"; // if username/password auth was used, then pass the sessionID if (credential.Contains(":")) { webRequest.Headers.Add("Cookie", "BITBUCKETSESSIONID= " + sessID); } // if user just specified http access token else { webRequest.Headers.Add("Authorization", "Bearer " + credential); } // set body and send request using (var streamWriter = new StreamWriter(webRequest.GetRequestStream())) { string json = "{\"query\":\"" + options + "\",\"entities\":{\"code\":{}},\"limits\":{\"primary\":100,\"secondary\":100}}"; streamWriter.Write(json); } // get web response WebResponse myWebResponse = await webRequest.GetResponseAsync(); string content; var reader = new StreamReader(myWebResponse.GetResponseStream()); content = reader.ReadToEnd(); bool validCreds = false; // figure out if creds valid for (int i = 0; i < myWebResponse.Headers.Count; ++i) { if (myWebResponse.Headers.Keys[i].ToString().ToLower().Equals("x-auserid")) { validCreds = true; } } if (validCreds) { // parse the JSON output and display results JsonTextReader jsonResult = new JsonTextReader(new StringReader(content)); string matchingLine = ""; string propName = ""; string projKey = ""; string repoName = ""; string fileName = ""; string nextPageStart = ""; string isLastPage = ""; // read the json results while (jsonResult.Read()) { switch (jsonResult.TokenType.ToString()) { case "StartObject": break; case "EndObject": break; case "StartArray": break; case "EndArray": // add the match to the list if (!matchingLine.Equals("") && !fileName.Equals("")) { bool alreadyExists = false; string fullURL = await library.BitbucketUtils.GetFullBitbucketRepoURLAsync(credential, sessID, projKey, repoName, url); URLResult singleResults = new URLResult(fullURL, matchingLine, fileName); // only add if not already found before foreach (URLResult item in urlResults) { if (item.fileName.Equals(fileName) && item.url.Equals(fullURL)) { alreadyExists = true; } } if (!alreadyExists) { urlResults.Add(singleResults); } } break; case "PropertyName": propName = jsonResult.Value.ToString(); break; case "String": // get the actual values if (propName.ToLower().Equals("text")) { if (jsonResult.Value.ToString().ToLower().Contains(options.ToLower())) { matchingLine = jsonResult.Value.ToString(); } } if (propName.ToLower().Equals("key")) { projKey = jsonResult.Value.ToString(); } if (propName.ToLower().Equals("slug")) { repoName = jsonResult.Value.ToString(); } if (propName.ToLower().Equals("file")) { fileName = jsonResult.Value.ToString(); } break; case "Integer": if (propName.ToLower().Equals("nextstart")) { nextPageStart = jsonResult.Value.ToString(); } break; case "Boolean": if (propName.ToLower().Equals("islastpage")) { isLastPage = jsonResult.Value.ToString(); } break; default: break; } } // iterate through the dictionary of matching lines and print them foreach (var item in urlResults) { if (item.fileName.ToLower().Contains(options.ToLower())) { Console.WriteLine("\n[>] REPO: " + item.url); Console.WriteLine(" [>] FILE: " + item.fileName); matchCount++; } } // if there are more pages, then make subsequent requests if (!nextPageStart.Equals("") && isLastPage.Equals("False")) { await makeSubsequentRequestAsync(credential, url, options, sessID, nextPageStart); } Console.WriteLine(""); Console.WriteLine("Total matching results: " + matchCount); } // if creds invalid else { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Credentials (username/password OR API token) provided are not valid."); Console.WriteLine(""); } } } catch (Exception ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Could not perform repo search. Exception: " + ex.ToString()); Console.WriteLine(""); } } // this is just placeholder for making more than 1 request due to paging of results. will have better global solution at some point than this band-aid public static async Task makeSubsequentRequestAsync(string credential, string url, string options, string sessID, string nextPage) { try { urlResults.Clear(); ServicePointManager.ServerCertificateValidationCallback = delegate (object s, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { return true; }; ServicePointManager.Expect100Continue = true; ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; // web request to search files via REST api var webRequest = (HttpWebRequest)System.Net.WebRequest.Create(url + "/rest/search/latest/search"); if (webRequest != null) { // set header values webRequest.Method = "POST"; webRequest.ContentType = "application/json"; webRequest.UserAgent = "SCMKIT-5dc493ada400c79dd318abbe770dac7c"; // if username/password auth was used, then pass the sessionID if (credential.Contains(":")) { webRequest.Headers.Add("Cookie", "BITBUCKETSESSIONID= " + sessID); } // if user just specified http access token else { webRequest.Headers.Add("Authorization", "Bearer " + credential); } // set body and send request using (var streamWriter = new StreamWriter(webRequest.GetRequestStream())) { string json = "{\"query\":\"" + options + "\",\"entities\":{\"code\":{\"start\":" + nextPage + "}},\"limits\":{\"primary\":100,\"secondary\":100}}"; streamWriter.Write(json); } // get web response WebResponse myWebResponse = await webRequest.GetResponseAsync(); string content; var reader = new StreamReader(myWebResponse.GetResponseStream()); content = reader.ReadToEnd(); bool validCreds = false; // figure out if creds valid for (int i = 0; i < myWebResponse.Headers.Count; ++i) { if (myWebResponse.Headers.Keys[i].ToString().ToLower().Equals("x-auserid")) { validCreds = true; } } if (validCreds) { // parse the JSON output and display results JsonTextReader jsonResult = new JsonTextReader(new StringReader(content)); string matchingLine = ""; string propName = ""; string projKey = ""; string repoName = ""; string fileName = ""; string nextPageStart = ""; string isLastPage = ""; // read the json results while (jsonResult.Read()) { switch (jsonResult.TokenType.ToString()) { case "StartObject": break; case "EndObject": break; case "StartArray": break; case "EndArray": // add the match to the list if (!matchingLine.Equals("") && !fileName.Equals("")) { bool alreadyExists = false; string fullURL = await library.BitbucketUtils.GetFullBitbucketRepoURLAsync(credential, sessID, projKey, repoName, url); URLResult singleResults = new URLResult(fullURL, matchingLine, fileName); // only add if not already found before foreach (URLResult item in urlResults) { if (item.fileName.Equals(fileName) && item.url.Equals(fullURL)) { alreadyExists = true; } } if (!alreadyExists) { urlResults.Add(singleResults); } } break; case "PropertyName": propName = jsonResult.Value.ToString(); break; case "String": // get the actual values if (propName.ToLower().Equals("text")) { if (jsonResult.Value.ToString().ToLower().Contains(options.ToLower())) { matchingLine = jsonResult.Value.ToString(); } } if (propName.ToLower().Equals("key")) { projKey = jsonResult.Value.ToString(); } if (propName.ToLower().Equals("slug")) { repoName = jsonResult.Value.ToString(); } if (propName.ToLower().Equals("file")) { fileName = jsonResult.Value.ToString(); } break; case "Integer": if (propName.ToLower().Equals("nextstart")) { nextPageStart = jsonResult.Value.ToString(); } break; case "Boolean": if (propName.ToLower().Equals("islastpage")) { isLastPage = jsonResult.Value.ToString(); } break; default: break; } } // iterate through the dictionary of matching lines and print them foreach (var item in urlResults) { if (item.fileName.ToLower().Contains(options.ToLower())) { Console.WriteLine("\n[>] REPO: " + item.url); Console.WriteLine(" [>] FILE: " + item.fileName); matchCount++; } } // if there are more pages, then make subsequent requests if (!nextPageStart.Equals("") && isLastPage.Equals("False")) { await makeSubsequentRequestAsync(credential, url, options, sessID, nextPageStart); } } // if creds invalid else { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Credentials (username/password OR API token) provided are not valid."); Console.WriteLine(""); } } } catch (Exception ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Could not perform file search. Exception: " + ex.ToString()); Console.WriteLine(""); } } } } ================================================ FILE: SCMKit/modules/bitbucket/ListPAT.cs ================================================ using System; using System.Linq; using System.Collections.Generic; using System.Net; using System.Threading.Tasks; using System.IO; using System.Security.Cryptography.X509Certificates; using System.Net.Security; namespace SCMKit.modules.bitbucket { class ListPAT { // hashtable to store mapping of PAT and permissions private static Dictionary patMapping = new Dictionary(); public static async Task execute(string credential, string url, string options, string system) { // Generate module header Console.WriteLine(library.Utils.GenerateHeader("listpat", credential, url, options, system)); try { string sessID = ""; // if username/password auth is used, get session ID for remainder of requests if (credential.Contains(":")) { sessID = library.BitbucketUtils.GetSessionID(credential, url); if (sessID == null) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Credentials provided are not valid."); Console.WriteLine(""); return; } } // create table header string tableHeader = string.Format("{0,15} | {1,30} | {2,50}", "ID", "Name", "Permissions"); Console.WriteLine(tableHeader); Console.WriteLine(new String('-', tableHeader.Length)); ServicePointManager.ServerCertificateValidationCallback = delegate (object s, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { return true; }; ServicePointManager.Expect100Continue = true; ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; await library.Utils.HeartbeatRequest(url); string[] splitCred = credential.Split(':'); string theUser = ""; if (options.Equals("")) { theUser = splitCred[0]; } else { theUser = options; } // web request to get pats via REST api var webRequest = (HttpWebRequest)System.Net.WebRequest.Create(url + "/rest/access-tokens/1.0/users/" + theUser); if (webRequest != null) { // set header values webRequest.Method = "GET"; webRequest.ContentType = "application/json"; webRequest.UserAgent = "SCMKIT-5dc493ada400c79dd318abbe770dac7c"; // if username/password auth was used, then pass the sessionID if (credential.Contains(":")) { webRequest.Headers.Add("Cookie", "BITBUCKETSESSIONID= " + sessID); } // if user just specified http access token else { webRequest.Headers.Add("Authorization", "Bearer " + credential); } // get web response WebResponse myWebResponse = await webRequest.GetResponseAsync(); string content; var reader = new StreamReader(myWebResponse.GetResponseStream()); content = reader.ReadToEnd(); bool validCreds = false; // figure out if creds valid for (int i = 0; i < myWebResponse.Headers.Count; ++i) { if (myWebResponse.Headers.Keys[i].ToString().ToLower().Equals("x-auserid")) { validCreds = true; } } if (validCreds) { // get all instances of PAT ID's IEnumerable startingIndexesPATids = library.Utils.AllIndexesOf(content, "\"id\":\""); IEnumerable endingIndexesPATids = library.Utils.AllIndexesOf(content, "\"createdDate\":"); List listOfPatIDs = new List(); for (int i = 0; i < startingIndexesPATids.Count(); i++) { string patID = ""; patID = content.Substring(startingIndexesPATids.ElementAt(i) + "\"id\":\"".Length, endingIndexesPATids.ElementAt(i) - startingIndexesPATids.ElementAt(i) - "\"id\":\"".Length); patID = patID.Replace("\"", ""); patID = patID.Replace(",", ""); listOfPatIDs.Add(patID); } // get all instances of the token name IEnumerable startingIndexesName = library.Utils.AllIndexesOf(content, "\"createdDate\":"); IEnumerable endingIndexesName = library.Utils.AllIndexesOf(content, "\"permissions\":"); List listOfPatNames = new List(); for (int i = 0; i < startingIndexesName.Count(); i++) { string patName = ""; patName = content.Substring(startingIndexesName.ElementAt(i) + "\"createdDate\":".Length, endingIndexesName.ElementAt(i) - startingIndexesName.ElementAt(i) - "\"createdDate\"".Length); patName = patName.Replace("\"", ""); patName = patName.Replace(",", ""); string[] patNameArray = patName.Split(':'); listOfPatNames.Add(patNameArray[patNameArray.Length - 1]); } // get all instances of the token permissions IEnumerable startingIndexesPermissions = library.Utils.AllIndexesOf(content, "\"permissions\":"); IEnumerable endingIndexesPermissions = library.Utils.AllIndexesOf(content, "\"user\":"); List listOfPatPermissions = new List(); for (int i = 0; i < startingIndexesPermissions.Count(); i++) { string patPermissions = ""; patPermissions = content.Substring(startingIndexesPermissions.ElementAt(i) + "\"permissions\":".Length, endingIndexesPermissions.ElementAt(i) - startingIndexesPermissions.ElementAt(i) - "\"permissions\"".Length); patPermissions = patPermissions.Replace("\"],\"", ""); patPermissions = patPermissions.Replace("[", ""); patPermissions = patPermissions.Replace("]", ""); patPermissions = patPermissions.Replace("\"", ""); listOfPatPermissions.Add(patPermissions); } // the pat names and permissions lists with have same counts, each index is associated with the other list, so list the permissions for each token for (int i = 0; i < listOfPatNames.Count(); i++) { Console.WriteLine("{0,15} | {1,30} | {2,50}", listOfPatIDs[i], listOfPatNames[i], listOfPatPermissions[i]); } } // if creds invalid else { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Credentials (username/password OR API token) provided are not valid."); Console.WriteLine(""); } } } catch (Exception ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Could not retrieve listing of PATs. Exception: " + ex.ToString()); Console.WriteLine(""); } } } } ================================================ FILE: SCMKit/modules/bitbucket/ListSSHKeys.cs ================================================ using System; using System.Linq; using System.Collections.Generic; using System.Net; using System.Threading.Tasks; using System.IO; using System.Security.Cryptography.X509Certificates; using System.Net.Security; namespace SCMKit.modules.bitbucket { class ListSSHKeys { public static async Task execute(string credential, string url, string options, string system) { // Generate module header Console.WriteLine(library.Utils.GenerateHeader("listsshkey", credential, url, options, system)); try { string sessID = ""; // if username/password auth is used, get session ID for remainder of requests if (credential.Contains(":")) { sessID = library.BitbucketUtils.GetSessionID(credential, url); if (sessID == null) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Credentials provided are not valid."); Console.WriteLine(""); return; } } // create table header string tableHeader = string.Format("{0,12} | {1,25} | {2,20}", "SSH Key ID", "SSH Key Value", "Label"); Console.WriteLine(tableHeader); Console.WriteLine(new String('-', tableHeader.Length)); ServicePointManager.ServerCertificateValidationCallback = delegate (object s, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { return true; }; ServicePointManager.Expect100Continue = true; ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; await library.Utils.HeartbeatRequest(url); string[] splitCred = credential.Split(':'); // web request to list SSH keys via REST API var webRequest = (HttpWebRequest)System.Net.WebRequest.Create(url + "/rest/ssh/1.0/keys"); if (webRequest != null) { // set header values webRequest.Method = "GET"; webRequest.ContentType = "application/json"; webRequest.UserAgent = "SCMKIT-5dc493ada400c79dd318abbe770dac7c"; // if username/password auth was used, then pass the sessionID if (credential.Contains(":")) { webRequest.Headers.Add("Cookie", "BITBUCKETSESSIONID= " + sessID); } // if user just specified http access token else { webRequest.Headers.Add("Authorization", "Bearer " + credential); } // get web response WebResponse myWebResponse = await webRequest.GetResponseAsync(); string content; var reader = new StreamReader(myWebResponse.GetResponseStream()); content = reader.ReadToEnd(); bool validCreds = false; // figure out if creds valid for (int i = 0; i < myWebResponse.Headers.Count; ++i) { if (myWebResponse.Headers.Keys[i].ToString().ToLower().Equals("x-auserid")) { validCreds = true; } } // only proceed if valid creds if (validCreds) { // get all instances of the SSH key id IEnumerable startingIndexesID = library.Utils.AllIndexesOf(content, "\"id\":"); IEnumerable endingIndexesID = library.Utils.AllIndexesOf(content, "\"text\":"); List listOfIndexIDs = new List(); for (int i = 0; i < startingIndexesID.Count(); i++) { string sshKeyID = ""; sshKeyID = content.Substring(startingIndexesID.ElementAt(i) + "\"id\":".Length, endingIndexesID.ElementAt(i) - startingIndexesID.ElementAt(i) - "\"id\"".Length); sshKeyID = sshKeyID.Replace("\"", ""); sshKeyID = sshKeyID.Replace(",", ""); listOfIndexIDs.Add(sshKeyID); } // get all instances of the SSH key value IEnumerable startingIndexesValue = library.Utils.AllIndexesOf(content, "\"text\":"); IEnumerable endingIndexesValue = library.Utils.AllIndexesOf(content, "\"label\":"); List listOfIndexValues = new List(); for (int i = 0; i < startingIndexesValue.Count(); i++) { string sshKeyValue = ""; sshKeyValue = content.Substring(startingIndexesValue.ElementAt(i) + "\"text\":".Length, endingIndexesValue.ElementAt(i) - startingIndexesValue.ElementAt(i) - "\"text\"".Length); sshKeyValue = sshKeyValue.Replace("\"", ""); sshKeyValue = sshKeyValue.Replace(",", ""); listOfIndexValues.Add(sshKeyValue); } // iterate through and print the ssh key ID's and contents for (int i = 0; i < listOfIndexIDs.Count(); i++) { string[] sshKeyArray = listOfIndexValues[i].Split(' '); string justSSHKey = sshKeyArray[1].Substring(sshKeyArray[1].Length - 20, 20); string justSSHLabel = ""; //the ssh key label is array value [2] if (sshKeyArray.Length == 3) { justSSHLabel = sshKeyArray[2]; } Console.WriteLine("{0,12} | {1,20} | {2,20}", listOfIndexIDs[i], "....." + justSSHKey, justSSHLabel); } } // if creds invalid else { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Credentials (username/password OR API token) provided are not valid."); Console.WriteLine(""); } } } catch (Exception ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Could not create SSH Key. Exception: " + ex.ToString()); Console.WriteLine(""); } } } } ================================================ FILE: SCMKit/modules/bitbucket/RemoveAdmin.cs ================================================ using System; using System.Net; using System.Threading.Tasks; using System.IO; using System.Security.Cryptography.X509Certificates; using System.Net.Security; namespace SCMKit.modules.bitbucket { class RemoveAdmin { public static async Task execute(string credential, string url, string options, string system) { // Generate module header Console.WriteLine(library.Utils.GenerateHeader("removeadmin", credential, url, options, system)); try { string sessID = ""; // if username/password auth is used, get session ID for remainder of requests if (credential.Contains(":")) { sessID = library.BitbucketUtils.GetSessionID(credential, url); if (sessID == null) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Credentials provided are not valid."); Console.WriteLine(""); return; } } // if API token was provided, display message and return else { Console.WriteLine(""); Console.WriteLine("[-] ERROR: API token authentication is not supported for this module. Please provide username/password with the appropriate permissions."); Console.WriteLine(""); return; } ServicePointManager.ServerCertificateValidationCallback = delegate (object s, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { return true; }; ServicePointManager.Expect100Continue = true; ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; await library.Utils.HeartbeatRequest(url); // web request to remove admin via rest API var webRequest = (HttpWebRequest)System.Net.WebRequest.Create(url + "/rest/api/1.0/admin/permissions/users?name=" + options + "&permission=PROJECT_CREATE"); if (webRequest != null) { // set header values webRequest.Method = "PUT"; webRequest.ContentType = "application/json"; webRequest.UserAgent = "SCMKIT-5dc493ada400c79dd318abbe770dac7c"; // if username/password auth was used, then pass the sessionID if (credential.Contains(":")) { webRequest.Headers.Add("Cookie", "BITBUCKETSESSIONID= " + sessID); } // if user just specified http access token else { webRequest.Headers.Add("Authorization", "Bearer " + credential); } // get web response WebResponse myWebResponse = await webRequest.GetResponseAsync(); string content; var reader = new StreamReader(myWebResponse.GetResponseStream()); content = reader.ReadToEnd(); bool validCreds = false; // figure out if creds valid for (int i = 0; i < myWebResponse.Headers.Count; ++i) { if (myWebResponse.Headers.Keys[i].ToString().ToLower().Equals("x-auserid")) { validCreds = true; } } if (validCreds) { Console.WriteLine(""); Console.WriteLine("[+] SUCCESS: Successfully removed " + options + " user from the admin role."); Console.WriteLine(""); } // if creds invalid else { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Credentials (username/password OR API token) provided are not valid."); Console.WriteLine(""); } } } catch (Exception ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Could not remove user from admin group. Exception: " + ex.ToString()); Console.WriteLine(""); } } } } ================================================ FILE: SCMKit/modules/bitbucket/RemovePAT.cs ================================================ using System; using System.Net; using System.Threading.Tasks; using System.IO; using System.Security.Cryptography.X509Certificates; using System.Net.Security; namespace SCMKit.modules.bitbucket { class RemovePAT { public static async Task execute(string credential, string url, string options, string system) { // Generate module header Console.WriteLine(library.Utils.GenerateHeader("removepat", credential, url, options, system)); try { string sessID = ""; // if username/password auth is used, get session ID for remainder of requests if (credential.Contains(":")) { sessID = library.BitbucketUtils.GetSessionID(credential, url); if (sessID == null) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Credentials provided are not valid."); Console.WriteLine(""); return; } } // if API token was provided, display message and return else { Console.WriteLine(""); Console.WriteLine("[-] ERROR: API token authentication is not supported for this module. Please provide username/password with the appropriate permissions."); Console.WriteLine(""); return; } ServicePointManager.ServerCertificateValidationCallback = delegate (object s, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { return true; }; ServicePointManager.Expect100Continue = true; ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; await library.Utils.HeartbeatRequest(url); string[] splitCred = credential.Split(':'); // if user didn't specify an ID, display message and return if (options.Equals("")) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Must supply ID of PAT to remove."); Console.WriteLine(""); return; } // web request to get pats via REST api var webRequest = (HttpWebRequest)System.Net.WebRequest.Create(url + "/rest/access-tokens/1.0/users/" + splitCred[0] + "/" + options); if (webRequest != null) { // set header values webRequest.Method = "DELETE"; webRequest.ContentType = "application/json"; webRequest.UserAgent = "SCMKIT-5dc493ada400c79dd318abbe770dac7c"; // if username/password auth was used, then pass the sessionID if (credential.Contains(":")) { webRequest.Headers.Add("Cookie", "BITBUCKETSESSIONID= " + sessID); } // if user just specified http access token else { webRequest.Headers.Add("Authorization", "Bearer " + credential); } // get web response WebResponse myWebResponse = await webRequest.GetResponseAsync(); string content; var reader = new StreamReader(myWebResponse.GetResponseStream()); content = reader.ReadToEnd(); bool validCreds = false; // figure out if creds valid for (int i = 0; i < myWebResponse.Headers.Count; ++i) { if (myWebResponse.Headers.Keys[i].ToString().ToLower().Equals("x-auserid")) { validCreds = true; } } if (validCreds) { Console.WriteLine(""); Console.WriteLine("[+] SUCCESS: The personal access token of ID " + options + " was successfully revoked."); Console.WriteLine(""); } // if creds invalid else { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Credentials (username/password OR API token) provided are not valid."); Console.WriteLine(""); } } } catch (Exception ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Could not remove PAT. Exception: " + ex.ToString()); Console.WriteLine(""); } } } } ================================================ FILE: SCMKit/modules/bitbucket/RemoveSSHKey.cs ================================================ using System; using System.Net; using System.Threading.Tasks; using System.IO; using System.Security.Cryptography.X509Certificates; using System.Net.Security; namespace SCMKit.modules.bitbucket { class RemoveSSHKey { public static async Task execute(string credential, string url, string options, string system) { // Generate module header Console.WriteLine(library.Utils.GenerateHeader("removesshkey", credential, url, options, system)); try { string sessID = ""; // if username/password auth is used, get session ID for remainder of requests if (credential.Contains(":")) { sessID = library.BitbucketUtils.GetSessionID(credential, url); if (sessID == null) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Credentials provided are not valid."); Console.WriteLine(""); return; } } // if API token was provided, display message and return else { Console.WriteLine(""); Console.WriteLine("[-] ERROR: API token authentication is not supported for this module. Please provide username/password with the appropriate permissions."); Console.WriteLine(""); return; } ServicePointManager.ServerCertificateValidationCallback = delegate (object s, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { return true; }; ServicePointManager.Expect100Continue = true; ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; await library.Utils.HeartbeatRequest(url); string[] splitCred = credential.Split(':'); // if user didn't specify an ID, display message and return if (options.Equals("")) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Must supply ID of SSH key to remove."); Console.WriteLine(""); return; } // web request to remove SSH key via REST API var webRequest = (HttpWebRequest)System.Net.WebRequest.Create(url + "/rest/ssh/1.0/keys/" + options); if (webRequest != null) { // set header values webRequest.Method = "DELETE"; webRequest.ContentType = "application/json"; webRequest.UserAgent = "SCMKIT-5dc493ada400c79dd318abbe770dac7c"; // if username/password auth was used, then pass the sessionID if (credential.Contains(":")) { webRequest.Headers.Add("Cookie", "BITBUCKETSESSIONID= " + sessID); } // if user just specified http access token else { webRequest.Headers.Add("Authorization", "Bearer " + credential); } // set body and send request using (var streamWriter = new StreamWriter(webRequest.GetRequestStream())) { string json = "{\"" + "text" + "\": \"" + options + "\"}"; streamWriter.Write(json); } // get web response HttpWebResponse myWebResponse = (HttpWebResponse)await webRequest.GetResponseAsync(); string content; var reader = new StreamReader(myWebResponse.GetResponseStream()); content = reader.ReadToEnd(); // if we got 204 response, SSH key was removed if (myWebResponse.StatusCode.ToString().ToLower().Equals("nocontent")) { Console.WriteLine(""); Console.WriteLine("[+] SUCCESS: The SSH key of ID " + options + " was successfully revoked."); Console.WriteLine(""); } else { Console.WriteLine(""); Console.WriteLine("[-] ERROR: There was an error revoking the SSH key."); Console.WriteLine(""); } } } catch (Exception ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Could not remove SSH Key. Exception: " + ex.ToString()); Console.WriteLine(""); } } } } ================================================ FILE: SCMKit/modules/bitbucket/RepoList.cs ================================================ using System; using System.Collections.Generic; using System.Net; using System.Threading.Tasks; using System.IO; using Newtonsoft.Json; using System.Security.Cryptography.X509Certificates; using System.Net.Security; namespace SCMKit.modules.bitbucket { class RepoList { // dictionary to hold the list of repos and their URLs private static Dictionary repoMapping = new Dictionary(); public static async Task execute(string credential, string url, string options, string system) { // Generate module header Console.WriteLine(library.Utils.GenerateHeader("listrepo", credential, url, options, system)); try { string sessID = ""; // if username/password auth is used, get session ID for remainder of requests if (credential.Contains(":")) { sessID = library.BitbucketUtils.GetSessionID(credential, url); if (sessID == null) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Credentials provided are not valid."); Console.WriteLine(""); return; } } // create table header string tableHeader = string.Format("{0,30} | {1,50}", "Name", "URL"); Console.WriteLine(tableHeader); Console.WriteLine(new String('-', tableHeader.Length)); ServicePointManager.ServerCertificateValidationCallback = delegate (object s, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { return true; }; ServicePointManager.Expect100Continue = true; ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; await library.Utils.HeartbeatRequest(url); // web request to get repos via REST api var webRequest = (HttpWebRequest)System.Net.WebRequest.Create(url + "/rest/api/1.0/repos?limit=25"); if (webRequest != null) { // set header values webRequest.Method = "GET"; webRequest.ContentType = "application/json"; webRequest.UserAgent = "SCMKIT-5dc493ada400c79dd318abbe770dac7c"; // if username/password auth was used, then pass the sessionID if (credential.Contains(":")) { webRequest.Headers.Add("Cookie", "BITBUCKETSESSIONID= " + sessID); } // if user just specified http access token else { webRequest.Headers.Add("Authorization", "Bearer " + credential); } // get web response WebResponse myWebResponse = await webRequest.GetResponseAsync(); string content; var reader = new StreamReader(myWebResponse.GetResponseStream()); content = reader.ReadToEnd(); bool validCreds = false; // figure out if creds valid for (int i = 0; i < myWebResponse.Headers.Count; ++i) { if (myWebResponse.Headers.Keys[i].ToString().ToLower().Equals("x-auserid")) { validCreds = true; } } if (validCreds) { // parse the JSON output and display results JsonTextReader jsonResult = new JsonTextReader(new StringReader(content)); string name = ""; string link = ""; string propName = ""; string nextPageStart = ""; // read the json results while (jsonResult.Read()) { switch (jsonResult.TokenType.ToString()) { case "StartObject": break; case "EndObject": break; case "StartArray": break; case "EndArray": // add repo name and link to dictionary if (!name.Equals("") && !link.Equals("")) { if (!repoMapping.ContainsKey(name.Trim()) && !repoMapping.ContainsValue(link.Trim())) { repoMapping.Add(name.Trim(), link); } } break; case "PropertyName": propName = jsonResult.Value.ToString(); break; case "String": if (propName.ToLower().Equals("slug")) { name = jsonResult.Value.ToString(); } if (propName.ToLower().Equals("href") && jsonResult.Value.ToString().EndsWith(".git") && jsonResult.Value.ToString().StartsWith("http")) { link = jsonResult.Value.ToString(); } break; case "Integer": if (propName.ToLower().Equals("nextpagestart")) { nextPageStart = jsonResult.Value.ToString(); } break; default: break; } } // iterate throught the dictionary of repos and print them foreach (var item in repoMapping) { Console.WriteLine("{0,30} | {1,50}", item.Key, item.Value); } // if there are more pages, then make subsequent requests if (!nextPageStart.Equals("")) { await makeSubsequentRequestAsync(credential, url, options, sessID, nextPageStart); } } // if creds invalid else { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Credentials (username/password OR API token) provided are not valid."); Console.WriteLine(""); } } } catch (Exception ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Could not retrieve listing of repos. Exception: " + ex.ToString()); Console.WriteLine(""); } } // this is just placeholder for making more than 1 request due to paging of results. will have better global solution at some point than this band-aid public static async Task makeSubsequentRequestAsync(string credential, string url, string options, string sessID, string nextPage) { try { repoMapping.Clear(); ServicePointManager.ServerCertificateValidationCallback = delegate (object s, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { return true; }; ServicePointManager.Expect100Continue = true; ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; // web request to get repos via REST api var webRequest = (HttpWebRequest)System.Net.WebRequest.Create(url + "/rest/api/1.0/repos?limit=25&start=" + nextPage); if (webRequest != null) { // set header values webRequest.Method = "GET"; webRequest.ContentType = "application/json"; webRequest.UserAgent = "SCMKIT-5dc493ada400c79dd318abbe770dac7c"; // if username/password auth was used, then pass the sessionID if (credential.Contains(":")) { webRequest.Headers.Add("Cookie", "BITBUCKETSESSIONID= " + sessID); } // if user just specified http access token else { webRequest.Headers.Add("Authorization", "Bearer " + credential); } // get web response WebResponse myWebResponse = await webRequest.GetResponseAsync(); string content; var reader = new StreamReader(myWebResponse.GetResponseStream()); content = reader.ReadToEnd(); bool validCreds = false; // figure out if creds valid for (int i = 0; i < myWebResponse.Headers.Count; ++i) { if (myWebResponse.Headers.Keys[i].ToString().ToLower().Equals("x-auserid")) { validCreds = true; } } if (validCreds) { // parse the JSON output and display results JsonTextReader jsonResult = new JsonTextReader(new StringReader(content)); string name = ""; string link = ""; string propName = ""; string nextPageStart = ""; // read the json results while (jsonResult.Read()) { switch (jsonResult.TokenType.ToString()) { case "StartObject": break; case "EndObject": break; case "StartArray": break; case "EndArray": // add repo name and link to dictionary if (!name.Equals("") && !link.Equals("")) { if (!repoMapping.ContainsKey(name.Trim()) && !repoMapping.ContainsValue(link.Trim())) { repoMapping.Add(name.Trim(), link); } } break; case "PropertyName": propName = jsonResult.Value.ToString(); break; case "String": if (propName.ToLower().Equals("slug")) { name = jsonResult.Value.ToString(); } if (propName.ToLower().Equals("href") && jsonResult.Value.ToString().EndsWith(".git") && jsonResult.Value.ToString().StartsWith("http")) { link = jsonResult.Value.ToString(); } break; case "Integer": if (propName.ToLower().Equals("nextpagestart")) { nextPageStart = jsonResult.Value.ToString(); } break; default: break; } } // iterate throught the dictionary of repos and print them foreach (var item in repoMapping) { Console.WriteLine("{0,30} | {1,50}", item.Key, item.Value); } // if there are more pages, then make subsequent requests if (!nextPageStart.Equals("")) { await makeSubsequentRequestAsync(credential, url, options, sessID, nextPageStart); } } // if creds invalid else { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Credentials (username/password OR API token) provided are not valid."); Console.WriteLine(""); } } } catch (Exception ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Could not retrieve listing of repos. Exception: " + ex.ToString()); Console.WriteLine(""); } } } } ================================================ FILE: SCMKit/modules/bitbucket/RepoSearch.cs ================================================ using System; using System.Collections.Generic; using System.Net; using System.Threading.Tasks; using System.IO; using Newtonsoft.Json; using System.Security.Cryptography.X509Certificates; using System.Net.Security; namespace SCMKit.modules.bitbucket { class RepoSearch { // dictionary to hold the list of repos and their URLs private static Dictionary repoMapping = new Dictionary(); private static int matchCount = 0; public static async Task execute(string credential, string url, string options, string system) { // Generate module header Console.WriteLine(library.Utils.GenerateHeader("searchrepo", credential, url, options, system)); try { string sessID = ""; // if username/password auth is used, get session ID for remainder of requests if (credential.Contains(":")) { sessID = library.BitbucketUtils.GetSessionID(credential, url); if (sessID == null) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Credentials provided are not valid."); Console.WriteLine(""); return; } } // create table header string tableHeader = string.Format("{0,30} | {1,50}", "Name", "URL"); Console.WriteLine(tableHeader); Console.WriteLine(new String('-', tableHeader.Length)); ServicePointManager.ServerCertificateValidationCallback = delegate (object s, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { return true; }; ServicePointManager.Expect100Continue = true; ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; await library.Utils.HeartbeatRequest(url); // web request to search via REST api var webRequest = (HttpWebRequest)System.Net.WebRequest.Create(url + "/rest/api/1.0/repos?limit=25&name=" + options); if (webRequest != null) { // set header values webRequest.Method = "GET"; webRequest.ContentType = "application/json"; webRequest.UserAgent = "SCMKIT-5dc493ada400c79dd318abbe770dac7c"; // if username/password auth was used, then pass the sessionID if (credential.Contains(":")) { webRequest.Headers.Add("Cookie", "BITBUCKETSESSIONID= " + sessID); } // if user just specified http access token else { webRequest.Headers.Add("Authorization", "Bearer " + credential); } // get web response WebResponse myWebResponse = await webRequest.GetResponseAsync(); string content; var reader = new StreamReader(myWebResponse.GetResponseStream()); content = reader.ReadToEnd(); bool validCreds = false; // figure out if creds valid for (int i = 0; i < myWebResponse.Headers.Count; ++i) { if (myWebResponse.Headers.Keys[i].ToString().ToLower().Equals("x-auserid")) { validCreds = true; } } if (validCreds) { // parse the JSON output and display results JsonTextReader jsonResult = new JsonTextReader(new StringReader(content)); string name = ""; string link = ""; string propName = ""; string nextPageStart = ""; // read the json results while (jsonResult.Read()) { switch (jsonResult.TokenType.ToString()) { case "StartObject": break; case "EndObject": break; case "StartArray": break; case "EndArray": // add repo name and link to dictionary if (!name.Equals("") && !link.Equals("")) { if (!repoMapping.ContainsKey(name.Trim())) { repoMapping.Add(name.Trim(), link); matchCount++; } } break; case "PropertyName": propName = jsonResult.Value.ToString(); break; case "String": if (propName.ToLower().Equals("name")) { if (jsonResult.Value.ToString().ToLower().Contains(options.ToLower())) { name = jsonResult.Value.ToString(); } } if (propName.ToLower().Equals("href") && jsonResult.Value.ToString().EndsWith(".git") && jsonResult.Value.ToString().StartsWith("http")) { link = jsonResult.Value.ToString(); } break; case "Integer": if (propName.ToLower().Equals("nextpagestart")) { nextPageStart = jsonResult.Value.ToString(); } break; default: break; } } // iterate throught the dictionary of repos and print them foreach (var item in repoMapping) { Console.WriteLine("{0,30} | {1,50}", item.Key, item.Value); } // if there are more pages, then make subsequent requests if (!nextPageStart.Equals("")) { await makeSubsequentRequestAsync(credential, url, options, sessID, nextPageStart); } Console.WriteLine(""); Console.WriteLine("Total matching results: " + matchCount); } // if creds invalid else { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Credentials (username/password OR API token) provided are not valid."); Console.WriteLine(""); } } } catch (Exception ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Could not perform repo search. Exception: " + ex.ToString()); Console.WriteLine(""); } } // this is just placeholder for making more than 1 request due to paging of results. will have better global solution at some point than this band-aid public static async Task makeSubsequentRequestAsync(string credential, string url, string options, string sessID, string nextPage) { try { repoMapping.Clear(); ServicePointManager.ServerCertificateValidationCallback = delegate (object s, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { return true; }; ServicePointManager.Expect100Continue = true; ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; // web request to get repos via REST api var webRequest = (HttpWebRequest)System.Net.WebRequest.Create(url + "/rest/api/1.0/repos?limit=25&start=" + nextPage + "&name=" + options); if (webRequest != null) { // set header values webRequest.Method = "GET"; webRequest.ContentType = "application/json"; webRequest.UserAgent = "SCMKIT-5dc493ada400c79dd318abbe770dac7c"; // if username/password auth was used, then pass the sessionID if (credential.Contains(":")) { webRequest.Headers.Add("Cookie", "BITBUCKETSESSIONID= " + sessID); } // if user just specified http access token else { webRequest.Headers.Add("Authorization", "Bearer " + credential); } // get web response WebResponse myWebResponse = await webRequest.GetResponseAsync(); string content; var reader = new StreamReader(myWebResponse.GetResponseStream()); content = reader.ReadToEnd(); bool validCreds = false; // figure out if creds valid for (int i = 0; i < myWebResponse.Headers.Count; ++i) { if (myWebResponse.Headers.Keys[i].ToString().ToLower().Equals("x-auserid")) { validCreds = true; } } if (validCreds) { // parse the JSON output and display results JsonTextReader jsonResult = new JsonTextReader(new StringReader(content)); string name = ""; string link = ""; string propName = ""; string nextPageStart = ""; // read the json results while (jsonResult.Read()) { switch (jsonResult.TokenType.ToString()) { case "StartObject": break; case "EndObject": break; case "StartArray": break; case "EndArray": // add repo name and link to dictionary if (!name.Equals("") && !link.Equals("")) { if (!repoMapping.ContainsKey(name.Trim())) { repoMapping.Add(name.Trim(), link); matchCount++; } } break; case "PropertyName": propName = jsonResult.Value.ToString(); break; case "String": if (propName.ToLower().Equals("name")) { if (jsonResult.Value.ToString().ToLower().Contains(options.ToLower())) { name = jsonResult.Value.ToString(); } } if (propName.ToLower().Equals("href") && jsonResult.Value.ToString().EndsWith(".git") && jsonResult.Value.ToString().StartsWith("http")) { link = jsonResult.Value.ToString(); } break; case "Integer": if (propName.ToLower().Equals("nextpagestart")) { nextPageStart = jsonResult.Value.ToString(); } break; default: break; } } // iterate throught the dictionary of repos and print them foreach (var item in repoMapping) { Console.WriteLine("{0,30} | {1,50}", item.Key, item.Value); } // if there are more pages, then make subsequent requests if (!nextPageStart.Equals("")) { await makeSubsequentRequestAsync(credential, url, options, sessID, nextPageStart); } } // if creds invalid else { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Credentials (username/password OR API token) provided are not valid."); Console.WriteLine(""); } } } catch (Exception ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Could not perform repo search. Exception: " + ex.ToString()); Console.WriteLine(""); } } } } ================================================ FILE: SCMKit/modules/github/AddAdmin.cs ================================================ using Octokit; using System; using System.Net; using System.Net.Security; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; namespace SCMKit.modules.github { class AddAdmin { public static async Task execute(string credential, string url, string options, string system) { // Generate module header Console.WriteLine(library.Utils.GenerateHeader("addadmin", credential, url, options, system)); try { await library.Utils.HeartbeatRequest(url); ServicePointManager.ServerCertificateValidationCallback = delegate (object s, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { return true; }; GitHubClient client = library.GitHubUtils.AuthToGitHub(credential, url); await client.User.Administration.Promote(options); Console.WriteLine("[+] SUCCESS: The user " + options + " has been added to site admins"); } catch (Exception ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Could not add user provided to site admin role. Exception: " + ex.ToString()); Console.WriteLine(""); } } } } ================================================ FILE: SCMKit/modules/github/AdminStats.cs ================================================ using System; using System.Threading.Tasks; using Octokit; namespace SCMKit.modules.github { class AdminStats { public static async Task execute(string credential, string url, string option, string system) { // Generate module header Console.WriteLine(library.Utils.GenerateHeader("adminstats", credential, url, option, system)); try { await library.Utils.HeartbeatRequest(url); GitHubClient client = library.GitHubUtils.AuthToGitHub(credential, url); var data = await client.Enterprise.AdminStats.GetStatisticsAll(); // create table headers string tableHeader = string.Format("{0,16} | {1,16} | {2,16}", "Admin Users", "Suspended Users", "Total Users"); Console.WriteLine(tableHeader); Console.WriteLine(new String('-', tableHeader.Length)); Console.WriteLine("{0,16} | {1,16} | {2,16}", data.Users.AdminUsers, data.Users.SuspendedUsers, data.Users.TotalUsers); Console.WriteLine(""); Console.WriteLine(""); string tableHeader2 = string.Format("{0,16} | {1,16}", "Total Repos", "Total Wikis"); Console.WriteLine(tableHeader2); Console.WriteLine(new String('-', tableHeader2.Length)); Console.WriteLine("{0,16} | {1,16}", data.Repos.TotalRepos, data.Repos.TotalWikis); Console.WriteLine(""); Console.WriteLine(""); string tableHeader3 = string.Format("{0,16} | {1,20} | {2,16}", "Total Orgs", "Total Team Members", "Total Teams"); Console.WriteLine(tableHeader3); Console.WriteLine(new String('-', tableHeader3.Length)); Console.WriteLine("{0,16} | {1,20} | {2,16}", data.Orgs.TotalOrgs, data.Orgs.TotalTeamMembers, data.Orgs.TotalTeams); Console.WriteLine(""); Console.WriteLine(""); string tableHeader4 = string.Format("{0,16} | {1,16}", "Private Gists", "Public Gists"); Console.WriteLine(tableHeader4); Console.WriteLine(new String('-', tableHeader4.Length)); Console.WriteLine("{0,16} | {1,16}", data.Gists.PrivateGists, data.Gists.PublicGists); } catch (Exception ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Could not retrieve listing of admin stats. Exception: " + ex.ToString()); Console.WriteLine(""); } } } } ================================================ FILE: SCMKit/modules/github/BranchProtection.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Newtonsoft.Json.Linq; using Octokit; using SCMKit.library; namespace SCMKit.modules.github { class BranchProtection { public static async Task execute(string credential, string url, string options, string system) { // Generate module header Console.WriteLine(library.Utils.GenerateHeader("protection", credential, url, options, system)); try { await library.Utils.HeartbeatRequest(url); GitHubClient client = library.GitHubUtils.AuthToGitHub(credential, url); // create table header string tableHeader = string.Format("{0,-25} | {1,-25} | {2,-50}", "Repo", "Branch", "Protection"); Console.WriteLine(tableHeader); Console.WriteLine(new String('-', tableHeader.Length)); IRepositoryBranchesClient branchesClient = new RepositoryBranchesClient(new ApiConnection(client.Connection)); SearchRepositoriesRequest searchString = new SearchRepositoriesRequest(); // adding search string in initilization if (!string.IsNullOrEmpty(options)) { searchString = new SearchRepositoriesRequest(options); // adding search string in initilization } searchString.Stars = Range.GreaterThanOrEquals(0); // using this as a hack to be able to get all repos listed var sa = await client.Search.SearchRepo(searchString); int totalNumItems = sa.TotalCount; // if total number of results is past 100, then iterate through all pages if (totalNumItems >= 100) { for (int i = 1; i < (totalNumItems / 100) + 2; i++) { searchString.Page = i; var search = await client.Search.SearchRepo(searchString); IReadOnlyList currentSearch = search.Items; foreach (Repository repo in currentSearch) { var branches = branchesClient.GetAll(repo.Id); foreach (Branch branch in branches.Result) { var bLen = branch.Name.Length; string protectionSettings = $"- Protected: {branch.Protected.ToString(),-58}"; if (branch.Protected) { protectionSettings = $"+ Protected: {branch.Protected.ToString(),-58}"; BranchProtectionSettings branchProtection = new BranchProtectionSettings(); try { branchProtection = await branchesClient.GetBranchProtection(repo.Id, branch.Name); } catch (NotFoundException ex) { protectionSettings += $"\n{" + ",58}Insufficient privileges to list branch protection rules"; } if (branchProtection != null) { // List branch protection rules in the order they are displayed in the GitHub web console if (branchProtection.RequiredPullRequestReviews != null) { protectionSettings += $"\n{" + ",58}Require a pull request before merging: True"; if (branchProtection.RequiredPullRequestReviews.RequiredApprovingReviewCount != 0) protectionSettings += $"\n{" + ",58} Approvals required before merge: {branchProtection.RequiredPullRequestReviews.RequiredApprovingReviewCount}"; if (branchProtection.RequiredPullRequestReviews.RequireCodeOwnerReviews) protectionSettings += $"\n{" + ",58} Owner review required before merge: {branchProtection.RequiredPullRequestReviews.RequireCodeOwnerReviews}"; // Add "Allow specified actors to bypass required pull requests" setting when added to Octokit (not supported as of 8/30/22, but supported by GitHub API bypass_pull_request_allowances property) string bypassPullRequestAllowancesResponse = await GitHubUtils.callGitHubApiGet(credential, repo.Url, $"/branches/{branch.Name}/protection/required_pull_request_reviews", ""); if (bypassPullRequestAllowancesResponse.Contains( "bypass_pull_request_allowances")) { protectionSettings += $"\n{" + ",58} Users who may bypass pull requests:"; JToken bypassPullRequestAllowanceUsers = JObject.Parse(bypassPullRequestAllowancesResponse)["bypass_pull_request_allowances"]; foreach (var child in bypassPullRequestAllowanceUsers.Children().Children().Children()) { JToken user = JObject.Parse(child.ToString()); protectionSettings += $"\n{" + ",58} {user["login"]}"; } } } if (branchProtection.RequiredStatusChecks != null) { protectionSettings += $"\n{" + ",58}Status checks must pass before merge:"; if (branchProtection.RequiredStatusChecks.Strict) protectionSettings += $"\n{" + ",58} Branch must be up-to-date before merge: {branchProtection.RequiredStatusChecks.Strict}"; if (branchProtection.RequiredStatusChecks.Contexts != null) { // Context is deprecated, replace with Checks when added to Octokit (not supported as of 8/30/22, but supported by GitHub API) foreach (string statusCheck in branchProtection.RequiredStatusChecks.Contexts) protectionSettings += $"\n{" + ",58}\n {statusCheck}"; } } if (branchProtection.RequiredConversationResolution != null) protectionSettings += $"\n{" + ",58}Require conversation resolution: {branchProtection.RequiredConversationResolution.Enabled}"; if (branchProtection.RequiredSignatures != null) protectionSettings += $"\n{" + ",58}Require signed commits: {branchProtection.RequiredSignatures.Enabled}"; if (branchProtection.RequiredLinearHistory != null) protectionSettings += $"\n{" + ",58}Require linear history: {branchProtection.RequiredLinearHistory.Enabled}"; // Add "Require deployments to succeed before merging" setting when added to GitHub API and Octokit (not supported as of 8/30/22) if (branchProtection.EnforceAdmins != null) { if (branchProtection.EnforceAdmins.Enabled) protectionSettings += $"\n{" + ",58}Protections apply to repo admins: {branchProtection.EnforceAdmins.Enabled}"; } if (branchProtection.Restrictions != null) { protectionSettings += $"\n{" + ",58}Push restricted to:"; if (branchProtection.Restrictions.Users.Count > 0) { protectionSettings += $"\n{" + ",58} Users:"; foreach (var user in branchProtection.Restrictions.Users) protectionSettings += $"\n{" + ",58} {user.Login}"; } if (branchProtection.Restrictions.Teams.Count > 0) { protectionSettings += $"\n{" + ",58} Teams:"; foreach (var team in branchProtection.Restrictions.Teams) protectionSettings += $"\n{" + ",58} {team.Name}"; } // Add Apps when supported by Octokit (not supported as of 8/30/22, but supported by GitHub API) } if (branchProtection.AllowForcePushes != null) protectionSettings += $"\n{" + ",58}Allow force pushes: {branchProtection.AllowForcePushes.Enabled}"; if (branchProtection.AllowDeletions != null) protectionSettings += $"\n{" + ",58}Allow deletions: {branchProtection.AllowDeletions.Enabled}"; if (branchProtection.BlockCreations != null) protectionSettings += $"\n{" + ",58}Block creations: {branchProtection.BlockCreations.Enabled}"; } } Console.WriteLine("{0,-25} | {1,-25} | {2,-50}", repo.Name, branch.Name, protectionSettings); } } } } // if number of results is less than 100, just display the 1 page else { var search = await client.Search.SearchRepo(searchString); IReadOnlyList currentSearch = search.Items; foreach (Repository repo in currentSearch) { var rLen = repo.Name.Length; var branches = branchesClient.GetAll(repo.Id); foreach (Branch branch in branches.Result) { var bLen = branch.Name.Length; string protectionSettings = $"- Protected: {branch.Protected.ToString(),-58}"; if (branch.Protected) { protectionSettings = $"+ Protected: {branch.Protected.ToString(),-58}"; BranchProtectionSettings branchProtection = new BranchProtectionSettings(); try { branchProtection = await branchesClient.GetBranchProtection(repo.Id, branch.Name); } catch (NotFoundException ex) { protectionSettings += $"\n{" + ",58}Insufficient privileges to list branch protection rules"; } if (branchProtection != null) { // List branch protection rules in the order they are displayed in the GitHub web console if (branchProtection.RequiredPullRequestReviews != null) { protectionSettings += $"\n{" + ",58}Require a pull request before merging: True"; if (branchProtection.RequiredPullRequestReviews.RequiredApprovingReviewCount != 0) protectionSettings += $"\n{" + ",58} Approvals required before merge: {branchProtection.RequiredPullRequestReviews.RequiredApprovingReviewCount}"; if (branchProtection.RequiredPullRequestReviews.RequireCodeOwnerReviews) protectionSettings += $"\n{" + ",58} Owner review required before merge: {branchProtection.RequiredPullRequestReviews.RequireCodeOwnerReviews}"; // Add "Allow specified actors to bypass required pull requests" setting when added to Octokit (not supported as of 8/30/22, but supported by GitHub API bypass_pull_request_allowances property) string bypassPullRequestAllowancesResponse = await GitHubUtils.callGitHubApiGet(credential, repo.Url, $"/branches/{branch.Name}/protection/required_pull_request_reviews", ""); if (bypassPullRequestAllowancesResponse.Contains( "bypass_pull_request_allowances")) { protectionSettings += $"\n{" + ",58} Users who may bypass pull requests:"; JToken bypassPullRequestAllowanceUsers = JObject.Parse(bypassPullRequestAllowancesResponse)["bypass_pull_request_allowances"]; foreach (var child in bypassPullRequestAllowanceUsers.Children().Children().Children()) { JToken user = JObject.Parse(child.ToString()); protectionSettings += $"\n{" + ",58} {user["login"]}"; } } } if (branchProtection.RequiredStatusChecks != null) { protectionSettings += $"\n{" + ",58}Status checks must pass before merge:"; if (branchProtection.RequiredStatusChecks.Strict) protectionSettings += $"\n{" + ",58} Branch must be up-to-date before merge: {branchProtection.RequiredStatusChecks.Strict}"; if (branchProtection.RequiredStatusChecks.Contexts != null) { // Context is deprecated, replace with Checks when added to Octokit (not supported as of 8/30/22, but supported by GitHub API) foreach (string statusCheck in branchProtection.RequiredStatusChecks.Contexts) protectionSettings += $"\n{" + ",58}\n {statusCheck}"; } } if (branchProtection.RequiredConversationResolution != null) protectionSettings += $"\n{" + ",58}Require conversation resolution: {branchProtection.RequiredConversationResolution.Enabled}"; if (branchProtection.RequiredSignatures != null) protectionSettings += $"\n{" + ",58}Require signed commits: {branchProtection.RequiredSignatures.Enabled}"; if (branchProtection.RequiredLinearHistory != null) protectionSettings += $"\n{" + ",58}Require linear history: {branchProtection.RequiredLinearHistory.Enabled}"; // Add "Require deployments to succeed before merging" setting when added to GitHub API and Octokit (not supported as of 8/30/22) if (branchProtection.EnforceAdmins != null) { if (branchProtection.EnforceAdmins.Enabled) protectionSettings += $"\n{" + ",58}Protections apply to repo admins: {branchProtection.EnforceAdmins.Enabled}"; } if (branchProtection.Restrictions != null) { protectionSettings += $"\n{" + ",58}Push restricted to:"; if (branchProtection.Restrictions.Users.Count > 0) { protectionSettings += $"\n{" + ",58} Users:"; foreach (var user in branchProtection.Restrictions.Users) protectionSettings += $"\n{" + ",58} {user.Login}"; } if (branchProtection.Restrictions.Teams.Count > 0) { protectionSettings += $"\n{" + ",58} Teams:"; foreach (var team in branchProtection.Restrictions.Teams) protectionSettings += $"\n{" + ",58} {team.Name}"; } // Add Apps when supported by Octokit (not supported as of 8/30/22, but supported by GitHub API) } if (branchProtection.AllowForcePushes != null) protectionSettings += $"\n{" + ",58}Allow force pushes: {branchProtection.AllowForcePushes.Enabled}"; if (branchProtection.AllowDeletions != null) protectionSettings += $"\n{" + ",58}Allow deletions: {branchProtection.AllowDeletions.Enabled}"; if (branchProtection.BlockCreations != null) protectionSettings += $"\n{" + ",58}Block creations: {branchProtection.BlockCreations.Enabled}"; } } Console.WriteLine("{0,-25} | {1,-25} | {2,-50}", repo.Name, branch.Name, protectionSettings); } } } } catch (Exception ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Exception: " + ex.ToString()); Console.WriteLine(""); } } } } ================================================ FILE: SCMKit/modules/github/CodeSearch.cs ================================================ using System; using System.Collections.Generic; using System.Text.RegularExpressions; using System.Threading.Tasks; using Octokit; namespace SCMKit.modules.github { class CodeSearch { public static async Task execute(string credential, string url, string options, string system) { // Generate module header Console.WriteLine(library.Utils.GenerateHeader("searchcode", credential, url, options, system)); try { await library.Utils.HeartbeatRequest(url); GitHubClient client = library.GitHubUtils.AuthToGitHub(credential, url); SearchCodeRequest s = new SearchCodeRequest(options); // adding search string in initilization var sa = await client.Search.SearchCode(s); int totalNumItems = sa.TotalCount; // if total number of results is past 100, then iterate through all pages if (totalNumItems >= 100) { for (int i = 1; i < (totalNumItems / 100) + 2; i++) { SearchCodeRequest searchString = new SearchCodeRequest(options); // adding search string in initilization searchString.Page = i; var search = await client.Search.SearchCode(searchString); IReadOnlyList currentSearch = search.Items; foreach (SearchCode item in currentSearch) { Console.WriteLine("\n[>] URL: " + item.HtmlUrl.Replace(" ", "")); await getLinesInFile(credential, item.GitUrl, options); } } } // if number of results is less than 100, just display the 1 page else { SearchCodeRequest searchString = new SearchCodeRequest(options); // adding search string in initilization var search = await client.Search.SearchCode(searchString); IReadOnlyList currentSearch = search.Items; foreach (SearchCode item in currentSearch) { Console.WriteLine("\n[>] URL: " + item.HtmlUrl.Replace(" ", "")); await getLinesInFile(credential, item.GitUrl, options); } } Console.WriteLine(""); Console.WriteLine("Total number of items matching search: " + totalNumItems); } catch (Exception ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Could not results of search. Exception: " + ex.ToString()); Console.WriteLine(""); } } public static async Task getLinesInFile(String credential, String url, String sTerm) { try { library.WebUtils.IgnoreSSL(); var webRequest = library.WebUtils.GenerateRawFileWebRequest(credential, url); var versionResponse = await library.WebUtils.GetRequestResponseString(webRequest); String sPattern = "(.+|)" + sTerm + "(.+|)"; MatchCollection match = Regex.Matches(versionResponse, sPattern, RegexOptions.IgnoreCase); if (match.Count > 0) { Console.WriteLine("[*] Match count : " + match.Count); foreach (Match m in match) { Console.WriteLine(" |_ " + m.Value.Trim()); } } else { Console.WriteLine("[+] Regex did not match file content.."); } } catch (Exception ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Could not retrieve file(s) for parsing. Exception: " + ex.ToString()); Console.WriteLine(""); } } } } ================================================ FILE: SCMKit/modules/github/CreatePAT.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace SCMKit.modules.github { // TODO class CreatePAT { public static async Task execute(string credential, string url, string options, string system) { // Generate module header Console.WriteLine(library.Utils.GenerateHeader("createpat", credential, url, options, system)); try { Console.WriteLine(""); Console.WriteLine("[-] ERROR: This module is currently not supported for this system."); Console.WriteLine(""); } catch (Exception ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Could not create PAT. Exception: " + ex.ToString()); Console.WriteLine(""); } } } } ================================================ FILE: SCMKit/modules/github/CreateSSHKey.cs ================================================ using Octokit; using System; using System.Net; using System.Net.Security; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; namespace SCMKit.modules.github { class CreateSSHKey { public static async Task execute(string credential, string url, string options, string system) { // Generate module header Console.WriteLine(library.Utils.GenerateHeader("createsshkey", credential, url, options, system)); try { await library.Utils.HeartbeatRequest(url); ServicePointManager.ServerCertificateValidationCallback = delegate (object s, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { return true; }; GitHubClient client = library.GitHubUtils.AuthToGitHub(credential, url); // create random key name Random rd = new Random(); const string allowedChars = "ABCDEFGHJKLMNOPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; char[] chars = new char[5]; for (int i = 0; i < 5; i++) { chars[i] = allowedChars[rd.Next(0, allowedChars.Length)]; } string sshKeyName = new string(chars); sshKeyName = "SCMKIT-" + sshKeyName; await client.User.GitSshKey.Create(new NewPublicKey(sshKeyName, options)); Console.WriteLine(""); Console.WriteLine("[+] SUCCESS: The user SSH key was successfully added."); Console.WriteLine(""); } catch (Exception ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Could not add user SSH key. Exception: " + ex.ToString()); Console.WriteLine(""); } } } } ================================================ FILE: SCMKit/modules/github/FileSearch.cs ================================================ using System; using System.Collections.Generic; using System.Threading.Tasks; using Octokit; namespace SCMKit.modules.github { // TODO class FileSearch { public static async Task execute(string credential, string url, string options, string system) { // Generate module header Console.WriteLine(library.Utils.GenerateHeader("searchfile", credential, url, options, system)); try { await library.Utils.HeartbeatRequest(url); GitHubClient client = library.GitHubUtils.AuthToGitHub(credential, url); SearchCodeRequest s = new SearchCodeRequest(options); // adding search string in initilization s.In = new[] { CodeInQualifier.Path }; var sa = await client.Search.SearchCode(s); int totalNumItems = sa.TotalCount; int matchCount = 0; // if total number of results is past 100, then iterate through all pages if (totalNumItems >= 100) { for (int i = 1; i < (totalNumItems / 100) + 2; i++) { SearchCodeRequest searchString = new SearchCodeRequest(options); // adding search string in initilization searchString.In = new[] { CodeInQualifier.Path }; searchString.Page = i; var search = await client.Search.SearchCode(searchString); IReadOnlyList currentSearch = search.Items; foreach (SearchCode item in currentSearch) { if (item.Name.ToLower().Contains(options.ToLower())) { Console.WriteLine("\n[>] REPO: " + item.Repository.HtmlUrl); Console.WriteLine(" [>] FILE: " + item.HtmlUrl.Replace(" ", "")); matchCount++; } } } } // if number of results is less than 100, just display the 1 page else { SearchCodeRequest searchString = new SearchCodeRequest(options); // adding search string in initilization searchString.In = new[] { CodeInQualifier.Path }; var search = await client.Search.SearchCode(searchString); IReadOnlyList currentSearch = search.Items; foreach (SearchCode item in currentSearch) { if (item.Name.ToLower().Contains(options.ToLower())) { Console.WriteLine("\n[>] REPO: " + item.Repository.HtmlUrl); Console.WriteLine(" [>] FILE: " + item.HtmlUrl.Replace(" ", "")); matchCount++; } } } Console.WriteLine(""); Console.WriteLine("Total number of items matching search: " + matchCount); } catch (Exception ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Could not perform file search for search term given. Exception: " + ex.ToString()); Console.WriteLine(""); } } } } ================================================ FILE: SCMKit/modules/github/GistList.cs ================================================ using System; using System.Collections.Generic; using System.Threading.Tasks; using Octokit; namespace SCMKit.modules.github { class GistList { public static async Task execute(string credential, string url, string options, string system) { // Generate module header Console.WriteLine(library.Utils.GenerateHeader("listgist", credential, url, options, system)); try { await library.Utils.HeartbeatRequest(url); GitHubClient client = library.GitHubUtils.AuthToGitHub(credential, url); var currentGists = await client.Gist.GetAll(); IEnumerator currentUserGists = currentGists.GetEnumerator(); // create table header string tableHeader = string.Format("{0,40} | {1,10} | {2,50}", "Description", "Visibility", "URL"); Console.WriteLine(tableHeader); Console.WriteLine(new String('-', tableHeader.Length)); while (currentUserGists.MoveNext()) { string visibility = "private"; if (currentUserGists.Current.Public) { visibility = "public"; } Console.WriteLine("{0,40} | {1,10} | {2,50}", currentUserGists.Current.Description, visibility, currentUserGists.Current.HtmlUrl); } } catch (Exception ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Could not retrieve listing of gists. Exception: " + ex.ToString()); Console.WriteLine(""); } } } } ================================================ FILE: SCMKit/modules/github/ListPAT.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace SCMKit.modules.github { class ListPAT { public static async Task execute(string credential, string url, string options, string system) { // Generate module header Console.WriteLine(library.Utils.GenerateHeader("listpat", credential, url, options, system)); try { Console.WriteLine(""); Console.WriteLine("[-] ERROR: This module is currently not supported for this system."); Console.WriteLine(""); } catch (Exception ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Could not list PAT Exception: " + ex.ToString()); Console.WriteLine(""); } } } } ================================================ FILE: SCMKit/modules/github/ListSSHKeys.cs ================================================ using Octokit; using System; using System.Collections.Generic; using System.Net; using System.Net.Security; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; namespace SCMKit.modules.github { class ListSSHKeys { public static async Task execute(string credential, string url, string options, string system) { // Generate module header Console.WriteLine(library.Utils.GenerateHeader("listsshkey", credential, url, options, system)); try { await library.Utils.HeartbeatRequest(url); ServicePointManager.ServerCertificateValidationCallback = delegate (object s, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { return true; }; GitHubClient client = library.GitHubUtils.AuthToGitHub(credential, url); var currentSSHKeys = await client.User.GitSshKey.GetAllForCurrent(); IEnumerator enumeratorCurrentSSHKeys = currentSSHKeys.GetEnumerator(); // create table header string tableHeader = string.Format("{0,12} | {1,25} | {2,20}", "SSH Key ID", "SSH Key Value", "Title"); Console.WriteLine(tableHeader); Console.WriteLine(new String('-', tableHeader.Length)); while (enumeratorCurrentSSHKeys.MoveNext()) { string[] sshKeyArray = enumeratorCurrentSSHKeys.Current.Key.Split(' '); string justSSHKey = sshKeyArray[1].Substring(sshKeyArray[1].Length - 20, 20); Console.WriteLine("{0,12} | {1,25} | {2,20}", enumeratorCurrentSSHKeys.Current.Id, "....." + justSSHKey, enumeratorCurrentSSHKeys.Current.Title); } } catch (Exception ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Could not list user SSH keys. Exception: " + ex.ToString()); Console.WriteLine(""); } } } } ================================================ FILE: SCMKit/modules/github/OrgList.cs ================================================ using System; using System.Collections.Generic; using System.Threading.Tasks; using Octokit; namespace SCMKit.modules.github { class OrgList { public static async Task execute(string credential, string url, string options, string system) { // Generate module header Console.WriteLine(library.Utils.GenerateHeader("listorg", credential, url, options, system)); try { await library.Utils.HeartbeatRequest(url); GitHubClient client = library.GitHubUtils.AuthToGitHub(credential, url); var currentOrgs = await client.Organization.GetAllForCurrent(); IEnumerator currentUserOrgs = currentOrgs.GetEnumerator(); // create table header string tableHeader = string.Format("{0,30} | {1,50}", "Name", "URL"); Console.WriteLine(tableHeader); Console.WriteLine(new String('-', tableHeader.Length)); while (currentUserOrgs.MoveNext()) { Console.WriteLine("{0,30} | {1,50}", currentUserOrgs.Current.Login, currentUserOrgs.Current.Url); } } catch (Exception ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Could not retrieve listing of organizations. Exception: " + ex.ToString()); Console.WriteLine(""); } } } } ================================================ FILE: SCMKit/modules/github/Privs.cs ================================================ using System; using System.Collections.Generic; using System.Net; using System.Net.Security; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; namespace SCMKit.modules.github { class Privs { public static async Task execute(string credential, string url, string options, string system) { Dictionary valuePairs = new Dictionary(); valuePairs.Add("repo", "Grants full access to repositories, including private repositories."); valuePairs.Add("repo:status", "Grants read/write access to public and private repository commit statuses."); valuePairs.Add("repo_deployment", "Grants access to deployment statuses for public and private repositories."); valuePairs.Add("public_repo", "Limits access to public repositories."); valuePairs.Add("repo:invite", "Grants accept/decline abilities for invitations to collaborate on a repository."); valuePairs.Add("security_events", "RW access to security events in code scanning and secret scanning APIs."); valuePairs.Add("admin:repo_hook", "Grants read, write, ping, and delete access to repository hooks in public and private repositories."); valuePairs.Add("write:repo_hook", "Grants read, write, and ping access to hooks in public or private repositories."); valuePairs.Add("read:repo_hook", "Grants read and ping access to hooks in public or private repositories."); valuePairs.Add("admin:org", "Fully manage the organization and its teams, projects, and memberships."); valuePairs.Add("write:org", "Read and write access to organization membership, organization projects, and team membership."); valuePairs.Add("read:org", "Read-only access to organization membership, organization projects, and team membership."); valuePairs.Add("admin:public_key", "Fully manage public keys."); valuePairs.Add("write:public_key", "Create, list, and view details for public keys."); valuePairs.Add("read:public_key", "List and view details for public keys."); valuePairs.Add("admin:org_hook", " Grants read, write, ping, and delete access to organization hooks"); valuePairs.Add("gist", "Grants write access to gists."); valuePairs.Add("notifications", "Read access to user's notifications"); valuePairs.Add("user", "Grants read/write access to profile info only"); valuePairs.Add("read:user", "Grants access to read a user's profile data."); valuePairs.Add("user:email", "Grants read access to a user's email addresses."); valuePairs.Add("user:follow", "Grants access to follow or unfollow other users."); valuePairs.Add("delete_repo", "Grants access to delete adminable repositories."); valuePairs.Add("write:discussion", "Allows read and write access for team discussions."); valuePairs.Add("read:discussion", "Allows read access for team discussions."); valuePairs.Add("write:packages", "Grants access to upload or publish a package in GitHub Packages. "); valuePairs.Add("read:packages", "Grants access to download or install packages from GitHub Packages."); valuePairs.Add("delete:packages", "Grants access to delete packages from GitHub Packages."); valuePairs.Add("admin:gpg_key", "Fully manage GPG keys."); valuePairs.Add("write:gpg_key", "Create, list, and view details for GPG keys."); valuePairs.Add("read:gpg_key", "List and view details for GPG keys."); valuePairs.Add("workflow", "Grants the ability to add and update GitHub Actions workflow files."); valuePairs.Add("site_admin", "Full site administrator access."); valuePairs.Add("admin:enterprise", "Full control of enterprise. One step below site admin."); // Generate module header Console.WriteLine(library.Utils.GenerateHeader("privs", credential, url, options, system)); List listOfPermissions = new List(); // if username/password auth being used if (credential.Contains(":")) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Privs module only supports API key authentication to determine privs of the API key given."); Console.WriteLine(""); } // if token auth being used else { // create table header string tableHeader = string.Format("{0,20} | {1,70}", "Name", "Description"); Console.WriteLine(tableHeader); Console.WriteLine(new String('-', tableHeader.Length)); try { ServicePointManager.ServerCertificateValidationCallback = delegate (object s, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { return true; }; ServicePointManager.Expect100Continue = true; ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; await library.Utils.HeartbeatRequest(url); var webRequest = System.Net.WebRequest.Create(url + "/api/v3"); if (webRequest != null) { webRequest.Method = "GET"; webRequest.ContentType = "application/json"; webRequest.Headers.Add("Authorization", "Token " + credential); WebResponse myWebResponse = await webRequest.GetResponseAsync(); // Display each header and it's key , associated with the response object. for (int i = 0; i < myWebResponse.Headers.Count; ++i) { if (myWebResponse.Headers.Keys[i].ToString().ToLower().Equals("x-oauth-scopes")) { string[] splitValues = myWebResponse.Headers[i].Split(','); foreach (string val in splitValues) { foreach (var item in valuePairs) { if (item.Key.Trim().ToLower().Equals(val.Trim().ToLower())) { Console.WriteLine("{0,20} | {1,70}", val.Trim(), item.Value); } } } } } // Release resources of response object. myWebResponse.Close(); } } catch (Exception ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Could not retrieve listing of privileges for current API token. Exception: " + ex.ToString()); Console.WriteLine(""); } } } } } ================================================ FILE: SCMKit/modules/github/RemoveAdmin.cs ================================================ using Octokit; using System; using System.Net; using System.Net.Security; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; namespace SCMKit.modules.github { class RemoveAdmin { public static async Task execute(string credential, string url, string options, string system) { // Generate module header Console.WriteLine(library.Utils.GenerateHeader("removeadmin", credential, url, options, system)); try { ServicePointManager.ServerCertificateValidationCallback = delegate (object s, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { return true; }; await library.Utils.HeartbeatRequest(url); GitHubClient client = library.GitHubUtils.AuthToGitHub(credential, url); await client.User.Administration.Demote(options); Console.WriteLine("[+] SUCCESS: The user " + options + " has been removed from site admins"); } catch (Exception ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Could not remove user provided from site admin role. Exception: " + ex.ToString()); Console.WriteLine(""); } } } } ================================================ FILE: SCMKit/modules/github/RemovePAT.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace SCMKit.modules.github { // TODO class RemovePAT { public static async Task execute(string credential, string url, string options, string system) { // Generate module header Console.WriteLine(library.Utils.GenerateHeader("removepat", credential, url, options, system)); try { Console.WriteLine(""); Console.WriteLine("[-] ERROR: This module is currently not supported for this system."); Console.WriteLine(""); } catch (Exception ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Could not remove PAT. Exception: " + ex.ToString()); Console.WriteLine(""); } } } } ================================================ FILE: SCMKit/modules/github/RemoveSSHKey.cs ================================================ using Octokit; using System; using System.Net; using System.Net.Security; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; namespace SCMKit.modules.github { class RemoveSSHKey { public static async Task execute(string credential, string url, string options, string system) { // Generate module header Console.WriteLine(library.Utils.GenerateHeader("removesshkey", credential, url, options, system)); try { // if user didn't specify an ID, display message and return if (options.Equals("")) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Must supply ID of SSH key to remove."); Console.WriteLine(""); return; } await library.Utils.HeartbeatRequest(url); ServicePointManager.ServerCertificateValidationCallback = delegate (object s, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { return true; }; GitHubClient client = library.GitHubUtils.AuthToGitHub(credential, url); await client.User.GitSshKey.Delete(Int32.Parse(options)); Console.WriteLine(""); Console.WriteLine("[+] SUCCESS: The user SSH key was successfully removed."); Console.WriteLine(""); } catch (Exception ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Could not remove user SSH key. Exception: " + ex.ToString()); Console.WriteLine(""); } } } } ================================================ FILE: SCMKit/modules/github/RepoList.cs ================================================ using System; using System.Collections.Generic; using System.Threading.Tasks; using Octokit; namespace SCMKit.modules.github { class RepoList { public static async Task execute(string credential, string url, string options, string system) { // Generate module header Console.WriteLine(library.Utils.GenerateHeader("listrepo", credential, url, options, system)); try { await library.Utils.HeartbeatRequest(url); GitHubClient client = library.GitHubUtils.AuthToGitHub(credential, url); // create table header string tableHeader = string.Format("{0,40} | {1,10} | {2,50}", "Name", "Visibility", "URL"); Console.WriteLine(tableHeader); Console.WriteLine(new String('-', tableHeader.Length)); SearchRepositoriesRequest s = new SearchRepositoriesRequest(); // adding search string in initilization s.Stars = Range.GreaterThanOrEquals(0); // using this as a hack to be able to get all repos listed var sa = await client.Search.SearchRepo(s); int totalNumItems = sa.TotalCount; // if total number of results is past 100, then iterate through all pages if (totalNumItems >= 100) { for (int i = 1; i < (totalNumItems / 100) + 2; i++) { SearchRepositoriesRequest searchString = new SearchRepositoriesRequest(); // adding search string in initilization searchString.Stars = Range.GreaterThanOrEquals(0); searchString.Page = i; var search = await client.Search.SearchRepo(searchString); IReadOnlyList currentSearch = search.Items; foreach (Repository item in currentSearch) { string name = ""; string visibility = "Unknown"; string itemURL = ""; if (item.Name != null) { name = item.Name; } if (item.Visibility.HasValue) { visibility = item.Visibility.Value.ToString(); } if (item.HtmlUrl != null) { itemURL = item.HtmlUrl.Replace(" ", ""); } Console.WriteLine("{0,40} | {1,10} | {2,50}", name, visibility, itemURL); } } } // if number of results is less than 100, just display the 1 page else { SearchRepositoriesRequest searchString = new SearchRepositoriesRequest(); // adding search string in initilization searchString.Stars = Range.GreaterThanOrEquals(0); var search = await client.Search.SearchRepo(searchString); IReadOnlyList currentSearch = search.Items; foreach (Repository item in currentSearch) { string name = ""; string visibility = "Unknown"; string itemURL = ""; if (item.Name != null) { name = item.Name; } if (item.Visibility.HasValue) { visibility = item.Visibility.Value.ToString(); } if (item.HtmlUrl != null) { itemURL = item.HtmlUrl.Replace(" ", ""); } Console.WriteLine("{0,40} | {1,10} | {2,50}", name, visibility, itemURL); } } } catch (Exception ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Could not retrieve listing of repos. Exception: " + ex.ToString()); Console.WriteLine(""); } } } } ================================================ FILE: SCMKit/modules/github/RepoSearch.cs ================================================ using System; using System.Collections.Generic; using System.Threading.Tasks; using Octokit; namespace SCMKit.modules.github { class RepoSearch { public static async Task execute(string credential, string url, string options, string system) { // Generate module header Console.WriteLine(library.Utils.GenerateHeader("searchrepo", credential, url, options, system)); try { await library.Utils.HeartbeatRequest(url); GitHubClient client = library.GitHubUtils.AuthToGitHub(credential, url); // create table header string tableHeader = string.Format("{0,40} | {1,10} | {2,50}", "Name", "Visibility", "URL"); Console.WriteLine(tableHeader); Console.WriteLine(new String('-', tableHeader.Length)); SearchRepositoriesRequest s = new SearchRepositoriesRequest(options); // adding search string in initilization var sa = await client.Search.SearchRepo(s); int totalNumItems = sa.TotalCount; // if total number of results is past 100, then iterate through all pages if (totalNumItems >= 100) { for (int i = 1; i < (totalNumItems / 100) + 2; i++) { SearchRepositoriesRequest searchString = new SearchRepositoriesRequest(options); // adding search string in initilization searchString.Page = i; var search = await client.Search.SearchRepo(searchString); IReadOnlyList currentSearch = search.Items; foreach (Repository item in currentSearch) { string name = ""; string visibility = "Not Found"; string itemURL = ""; if (item.Name != null) { name = item.Name; } if (item.Visibility.HasValue) { visibility = item.Visibility.Value.ToString(); } if (item.HtmlUrl != null) { itemURL = item.HtmlUrl.Replace(" ", ""); } Console.WriteLine("{0,40} | {1,10} | {2,50}", name, visibility, itemURL); } } } // if number of results is less than 100, just display the 1 page else { IReadOnlyList currentSearch = sa.Items; foreach (Repository item in currentSearch) { string name = ""; string visibility = "Unknown"; string itemURL = ""; if (item.Name != null) { name = item.Name; } if (item.Visibility.HasValue) { visibility = item.Visibility.Value.ToString(); } if (item.HtmlUrl != null) { itemURL = item.HtmlUrl.Replace(" ", ""); } Console.WriteLine("{0,40} | {1,10} | {2,50}", name, visibility, itemURL); } } Console.WriteLine(""); Console.WriteLine("Total number of items matching search: " + totalNumItems); } catch (Exception ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Could not perform repo search for search term given. Exception: " + ex.ToString()); Console.WriteLine(""); } } } } ================================================ FILE: SCMKit/modules/gitlab/AddAdmin.cs ================================================ using System; using System.Collections.Generic; using System.Net; using System.Net.Security; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; using System.IO; using GitLabApiClient; namespace SCMKit.modules.gitlab { class AddAdmin { public static async Task execute(string credential, string url, string options, string system) { // Generate module header Console.WriteLine(library.Utils.GenerateHeader("addadmin", credential, url, options, system)); // dictionary to hold lookup table user ID's and usernames Dictionary userMapping = new Dictionary(); try { // auth to GitLab and get list of all users Task authTask = library.GitLabUtils.AuthToGitLabAsync(credential, url); GitLabClient client = authTask.Result; var users = await client.Users.GetAsync(); // add associated user and user id to the dictionary for subsequent requests foreach (var user in users) { userMapping.Add(user.Username, user.Id.ToString()); } // proceed with request to add admin permissions for the user given ServicePointManager.ServerCertificateValidationCallback = delegate (object s, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { return true; }; ServicePointManager.Expect100Continue = true; ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; await library.Utils.HeartbeatRequest(url); string accessToken = ""; // if username/password auth being used, get the access token first. this is needed for subsequent API requests if (credential.Contains(":")) { accessToken = library.GitLabUtils.GetAccessToken(credential, url); } string theUserID = userMapping[options.ToLower()]; // get snippets for user var webRequest = (HttpWebRequest)System.Net.WebRequest.Create(url + "/api/v4/users/" + theUserID); if (webRequest != null) { // set header values webRequest.Method = "PUT"; webRequest.ContentType = "application/json"; webRequest.UserAgent = "SCMKIT-5dc493ada400c79dd318abbe770dac7c"; // if we need to pass the token obtained from the username/password auth if (credential.Contains(":")) { webRequest.Headers.Add("Authorization", "Bearer " + accessToken); } // if user just specified personal access token else { webRequest.Headers.Add("PRIVATE-TOKEN", credential); } // set body and sent request using (var streamWriter = new StreamWriter(webRequest.GetRequestStream())) { string json = "{\"admin\":\"true\"}"; streamWriter.Write(json); } // get the response and the access token var httpResponse = (HttpWebResponse)webRequest.GetResponse(); var streamReader = new StreamReader(httpResponse.GetResponseStream()); string result = streamReader.ReadToEnd(); Console.WriteLine(""); Console.WriteLine("[+] SUCCESS: The " + options + " user was successfully added to the admin role."); Console.WriteLine(""); } } catch (KeyNotFoundException ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: User provided does not exist to modify. Exception: " + ex.ToString()); Console.WriteLine(""); } catch (Exception ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Could not add user to admin role. Exception: " + ex.ToString()); Console.WriteLine(""); } } } } ================================================ FILE: SCMKit/modules/gitlab/CodeSearch.cs ================================================ using System; using System.Collections.Generic; using System.Net; using System.Net.Security; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; using System.IO; using Newtonsoft.Json; using GitLabApiClient; namespace SCMKit.modules.gitlab { // custom class to handle the JSON output of search result public class gitlabSearchResult { public string basename { get; set; } public string data { get; set; } public string path { get; set; } public string filename { get; set; } public string id { get; set; } public string reference { get; set; } public int startLine { get; set; } public int project_id { get; set; } public gitlabSearchResult() { } } class CodeSearch { public static async Task execute(string credential, string url, string options, string system) { // Generate module header Console.WriteLine(library.Utils.GenerateHeader("searchcode", credential, url, options, system)); // lists to hold project ID's and mappings List listOfProjectIds = new List(); Dictionary projectMapping = new Dictionary(); try { await library.Utils.HeartbeatRequest(url); // auth to GitLab and get list of all projects Task authTask = library.GitLabUtils.AuthToGitLabAsync(credential, url); GitLabClient client = authTask.Result; var projects = await client.Projects.GetAsync(); foreach (var project in projects) { listOfProjectIds.Add(project.Id.ToString()); projectMapping.Add(project.Id.ToString(), project.WebUrl); } ServicePointManager.ServerCertificateValidationCallback = delegate (object s, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { return true; }; ServicePointManager.Expect100Continue = true; ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; // proceed with performing the search since we have all the project ID's and mappings string accessToken = ""; // if username/password auth being used, get the access token first. this is needed for subsequent API requests if (credential.Contains(":")) { accessToken = library.GitLabUtils.GetAccessToken(credential, url); } int searchMatchCount = 0; // perform the search for code with search term against each project using the project search API foreach (string projectToSearch in listOfProjectIds) { var webRequest = (HttpWebRequest)System.Net.WebRequest.Create(url + "/api/v4/projects/" + projectToSearch + "/search?scope=blobs&search=" + options); if (webRequest != null) { // set header values webRequest.Method = "GET"; webRequest.ContentType = "application/json"; webRequest.UserAgent = "SCMKIT-5dc493ada400c79dd318abbe770dac7c"; // if we need to pass the token obtained from the username/password auth if (credential.Contains(":")) { webRequest.Headers.Add("Authorization", "Bearer " + accessToken); } // if user just specified personal access token else { webRequest.Headers.Add("PRIVATE-TOKEN", credential); } // get web response WebResponse myWebResponse = await webRequest.GetResponseAsync(); string content; var reader = new StreamReader(myWebResponse.GetResponseStream()); content = reader.ReadToEnd(); // parse the JSON output and display results List searchResults = JsonConvert.DeserializeObject>(content); foreach (gitlabSearchResult item in searchResults) { Console.WriteLine("\n[>] URL: " + projectMapping[projectToSearch] + "/" + item.path); // get the actual line where the match happened string[] lines = item.data.Split(new string[] { "\r\n", "\r", "\n" }, StringSplitOptions.None); foreach (string line in lines) { if (line.ToLower().Contains(options.ToLower())) { searchMatchCount++; Console.WriteLine(" |_ " + line.Trim()); } } } } } Console.WriteLine(""); Console.WriteLine("Total number of items matching code search: " + searchMatchCount); } catch (Exception ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Could not perform code search for search term given. Exception: " + ex.ToString()); Console.WriteLine(""); } } } } ================================================ FILE: SCMKit/modules/gitlab/CreatePAT.cs ================================================ using System; using System.Collections.Generic; using System.Net; using System.Net.Security; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; using System.IO; using GitLabApiClient; namespace SCMKit.modules.gitlab { class CreatePAT { public static async Task execute(string credential, string url, string options, string system) { // Generate module header Console.WriteLine(library.Utils.GenerateHeader("createpat", credential, url, options, system)); // dictionary to hold lookup table user ID's and usernames Dictionary userMapping = new Dictionary(); try { await library.Utils.HeartbeatRequest(url); // auth to GitLab and get list of all users Task authTask = library.GitLabUtils.AuthToGitLabAsync(credential, url); GitLabClient client = authTask.Result; var users = await client.Users.GetAsync(); // add associated user and user id to the dictionary for subsequent requests foreach (var user in users) { userMapping.Add(user.Username, user.Id.ToString()); } ServicePointManager.ServerCertificateValidationCallback = delegate (object s, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { return true; }; ServicePointManager.Expect100Continue = true; ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; // proceed with request to create PAT for the user given string accessToken = ""; // if username/password auth being used, get the access token first. this is needed for subsequent API requests if (credential.Contains(":")) { accessToken = library.GitLabUtils.GetAccessToken(credential, url); } string theUserID = userMapping[options.ToLower()]; // perform request to create personal access token var webRequest = (HttpWebRequest)System.Net.WebRequest.Create(url + "/api/v4/users/" + theUserID + "/personal_access_tokens"); if (webRequest != null) { // set header values webRequest.Method = "POST"; webRequest.ContentType = "application/x-www-form-urlencoded"; webRequest.UserAgent = "SCMKIT-5dc493ada400c79dd318abbe770dac7c"; // if we need to pass the token obtained from the username/password auth if (credential.Contains(":")) { webRequest.Headers.Add("Authorization", "Bearer " + accessToken); } // if user just specified personal access token else { webRequest.Headers.Add("PRIVATE-TOKEN", credential); } // set body and sent request using (var streamWriter = new StreamWriter(webRequest.GetRequestStream())) { // create random token name Random rd = new Random(); const string allowedChars = "ABCDEFGHJKLMNOPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; char[] chars = new char[5]; for (int i = 0; i < 5; i++) { chars[i] = allowedChars[rd.Next(0, allowedChars.Length)]; } string personalAccessTokenName = new string(chars); personalAccessTokenName = "SCMKIT-" + personalAccessTokenName; string body = "name=" + personalAccessTokenName + "&scopes[]=api&scopes[]=read_repository&scopes[]=write_repository"; streamWriter.Write(body); } // get the response and the access token var httpResponse = (HttpWebResponse)webRequest.GetResponse(); var streamReader = new StreamReader(httpResponse.GetResponseStream()); string result = streamReader.ReadToEnd(); // create table header string tableHeader = string.Format("{0,5} | {1,12} | {2,30}", "ID", "Name", "Token"); Console.WriteLine(tableHeader); Console.WriteLine(new String('-', tableHeader.Length)); int startIndexTokenID = result.IndexOf("\"id\":"); int endIndexTokenID = result.IndexOf("\"name\":"); string tokenID = result.Substring(startIndexTokenID + "\"id\":".Length, endIndexTokenID - startIndexTokenID - "\"name\"".Length); int startIndexTokenName = result.IndexOf("\"name\":"); int endIndexTokenName = result.IndexOf("\"revoked\":"); string tokenName = result.Substring(startIndexTokenName + "\"name\":".Length, endIndexTokenName - startIndexTokenName - "\"revoked\"".Length).Replace("\"", ""); int startIndexTokenContent = result.IndexOf("\"token\":"); int endIndexTokenContent = result.IndexOf("\"}"); string tokenContent = result.Substring(startIndexTokenContent + "\"token\":".Length, endIndexTokenContent - startIndexTokenContent - "\"token\":".Length).Replace("\"", ""); Console.WriteLine("{0,5} | {1,12} | {2,30}", tokenID, tokenName, tokenContent); Console.WriteLine(""); Console.WriteLine("[+] SUCCESS: The " + options + " user personal access token was successfully added."); Console.WriteLine(""); } } catch (KeyNotFoundException ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: User provided does not exist to create personal access token for. Exception: " + ex.ToString()); Console.WriteLine(""); } catch (Exception ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Could not create personal access token for user. Exception: " + ex.ToString()); Console.WriteLine(""); } } } } ================================================ FILE: SCMKit/modules/gitlab/CreateSSHKey.cs ================================================ using System; using System.Collections.Generic; using System.Net; using System.Net.Security; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; using System.IO; namespace SCMKit.modules.gitlab { class CreateSSHKey { public static async Task execute(string credential, string url, string options, string system) { // Generate module header Console.WriteLine(library.Utils.GenerateHeader("createsshkey", credential, url, options, system)); // dictionary to hold lookup table user ID's and usernames Dictionary userMapping = new Dictionary(); try { await library.Utils.HeartbeatRequest(url); ServicePointManager.ServerCertificateValidationCallback = delegate (object s, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { return true; }; ServicePointManager.Expect100Continue = true; ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; // proceed with request to add ssh key for given user string accessToken = ""; // if username/password auth being used, get the access token first. this is needed for subsequent API requests if (credential.Contains(":")) { accessToken = library.GitLabUtils.GetAccessToken(credential, url); } // perform request to create SSH key var webRequest = (HttpWebRequest)System.Net.WebRequest.Create(url + "/api/v4/user/keys"); if (webRequest != null) { // set header values webRequest.Method = "POST"; webRequest.ContentType = "application/json"; webRequest.UserAgent = "SCMKIT-5dc493ada400c79dd318abbe770dac7c"; // if we need to pass the token obtained from the username/password auth if (credential.Contains(":")) { webRequest.Headers.Add("Authorization", "Bearer " + accessToken); } // if user just specified personal access token else { webRequest.Headers.Add("PRIVATE-TOKEN", credential); } // set body and sent request using (var streamWriter = new StreamWriter(webRequest.GetRequestStream())) { // create random key name Random rd = new Random(); const string allowedChars = "ABCDEFGHJKLMNOPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; char[] chars = new char[5]; for (int i = 0; i < 5; i++) { chars[i] = allowedChars[rd.Next(0, allowedChars.Length)]; } string sshKeyName = new string(chars); sshKeyName = "SCMKIT-" + sshKeyName; string json = "{\"" + "title" + "\": \"" + sshKeyName + "\",\"key\":\"" + options + "\"}"; streamWriter.Write(json); } // get the response and the access token var httpResponse = (HttpWebResponse)webRequest.GetResponse(); var streamReader = new StreamReader(httpResponse.GetResponseStream()); string result = streamReader.ReadToEnd(); // create table header string tableHeader = string.Format("{0,12} | {1,15}", "SSH Key ID", "SSH Key Name"); Console.WriteLine(tableHeader); Console.WriteLine(new String('-', tableHeader.Length)); int startIndexTokenID = result.IndexOf("\"id\":"); int endIndexTokenID = result.IndexOf("\"title\":"); string tokenID = result.Substring(startIndexTokenID + "\"id\":".Length, endIndexTokenID - startIndexTokenID - "\"id\"".Length); tokenID = tokenID.Replace("\"", ""); tokenID = tokenID.Replace(",", ""); int startIndexTokenName = result.IndexOf("\"title\":"); int endIndexTokenName = result.IndexOf("\"created_at\":"); string tokenName = result.Substring(startIndexTokenName + "\"title\":".Length, endIndexTokenName - startIndexTokenName - "\"title\"".Length).Replace("\"", ""); tokenName = tokenName.Replace(",", ""); Console.WriteLine("{0,12} | {1,15}", tokenID, tokenName); Console.WriteLine(""); Console.WriteLine("[+] SUCCESS: The user SSH key was successfully added."); Console.WriteLine(""); } } catch (KeyNotFoundException ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: User provided does not exist to create SSH key for. Exception: " + ex.ToString()); Console.WriteLine(""); } catch (Exception ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Could not create SSH key for user. Exception: " + ex.ToString()); Console.WriteLine(""); } } } } ================================================ FILE: SCMKit/modules/gitlab/FileSearch.cs ================================================ using System; using System.Collections.Generic; using System.Net; using System.Net.Security; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; using System.IO; using Newtonsoft.Json; using GitLabApiClient; namespace SCMKit.modules.gitlab { // custom class to handle the JSON output of file search result public class fileSearchResult { public string id { get; set; } public string name { get; set; } public string type { get; set; } public string path { get; set; } public string mode { get; set; } public fileSearchResult() { } } class FileSearch { public static async Task execute(string credential, string url, string options, string system) { // Generate module header Console.WriteLine(library.Utils.GenerateHeader("searchfile", credential, url, options, system)); // lists to hold project ID's and mappings List listOfProjectIds = new List(); Dictionary projectMapping = new Dictionary(); try { await library.Utils.HeartbeatRequest(url); // auth to GitLab and get list of all projects Task authTask = library.GitLabUtils.AuthToGitLabAsync(credential, url); GitLabClient client = authTask.Result; var projects = await client.Projects.GetAsync(); foreach (var project in projects) { listOfProjectIds.Add(project.Id.ToString()); projectMapping.Add(project.Id.ToString(), project.WebUrl); } ServicePointManager.ServerCertificateValidationCallback = delegate (object s, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { return true; }; ServicePointManager.Expect100Continue = true; ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; // proceed with performing the search since we have all the project ID's and mappings string accessToken = ""; // if username/password auth being used, get the access token first. this is needed for subsequent API requests if (credential.Contains(":")) { accessToken = library.GitLabUtils.GetAccessToken(credential, url); } int searchMatchCount = 0; // perform the search for files with search term against each project using the project search API foreach (string projectToSearch in listOfProjectIds) { var webRequest = (HttpWebRequest)System.Net.WebRequest.Create(url + "/api/v4/projects/" + projectToSearch + "/repository/tree?recursive=true&per_page=100"); if (webRequest != null) { // set header values webRequest.Method = "GET"; webRequest.ContentType = "application/json"; webRequest.UserAgent = "SCMKIT-5dc493ada400c79dd318abbe770dac7c"; // if we need to pass the token obtained from the username/password auth if (credential.Contains(":")) { webRequest.Headers.Add("Authorization", "Bearer " + accessToken); } // if user just specified personal access token else { webRequest.Headers.Add("PRIVATE-TOKEN", credential); } // get web response WebResponse myWebResponse = await webRequest.GetResponseAsync(); string content; var reader = new StreamReader(myWebResponse.GetResponseStream()); content = reader.ReadToEnd(); // parse the JSON output and display results List searchResults = JsonConvert.DeserializeObject>(content); foreach (fileSearchResult item in searchResults) { if (item.name.ToLower().Contains(options.ToLower())) { if (item.type.ToLower().Equals("blob")) { Console.WriteLine("\n[>] URL: " + projectMapping[projectToSearch] + "/" + item.path); searchMatchCount++; } } } } } Console.WriteLine(""); Console.WriteLine("Total number of items matching file search: " + searchMatchCount); } catch (Exception ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Could not perform file search for search term given. Exception: " + ex.ToString()); Console.WriteLine(""); } } } } ================================================ FILE: SCMKit/modules/gitlab/ListPAT.cs ================================================ using System; using System.Collections.Generic; using System.Net; using System.Net.Security; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; using System.IO; using Newtonsoft.Json; using GitLabApiClient; namespace SCMKit.modules.gitlab { // custom class to handle the JSON output of personal access token public class patResult { public int id { get; set; } public string name { get; set; } public bool revoked { get; set; } public string createdAt { get; set; } public string[] scopes { get; set; } public int user_id { get; set; } public bool active { get; set; } public string expires_at { get; set; } public patResult() { } } class ListPAT { public static async Task execute(string credential, string url, string options, string system) { // Generate module header Console.WriteLine(library.Utils.GenerateHeader("listpat", credential, url, options, system)); // dicationary to hold lookup table user ID's and usernames Dictionary userMapping = new Dictionary(); try { await library.Utils.HeartbeatRequest(url); // auth to GitLab and get list of all users Task authTask = library.GitLabUtils.AuthToGitLabAsync(credential, url); GitLabClient client = authTask.Result; var users = await client.Users.GetAsync(); // add associated user and user id to the dictionary for subsequent requests foreach (var user in users) { userMapping.Add(user.Username, user.Id.ToString()); } ServicePointManager.ServerCertificateValidationCallback = delegate (object s, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { return true; }; ServicePointManager.Expect100Continue = true; ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; // proceed with request to list PATs for the user given string accessToken = ""; // if username/password auth being used, get the access token first. this is needed for subsequent API requests if (credential.Contains(":")) { accessToken = library.GitLabUtils.GetAccessToken(credential, url); } string theUserID = userMapping[options.ToLower()]; // get listing of all personal access tokens for the user // create table header string tableHeader = string.Format("{0,5} | {1,20} | {2,10} | {3,50}", "ID", "Name", "Active?", "Scopes"); Console.WriteLine(tableHeader); Console.WriteLine(new String('-', tableHeader.Length)); // get personal access tokens for user var webRequestPersonalAccessTokens = (HttpWebRequest)System.Net.WebRequest.Create(url + "/api/v4/personal_access_tokens?user_id=" + theUserID); if (webRequestPersonalAccessTokens != null) { // set header values webRequestPersonalAccessTokens.Method = "GET"; webRequestPersonalAccessTokens.ContentType = "application/json"; webRequestPersonalAccessTokens.UserAgent = "SCMKIT-5dc493ada400c79dd318abbe770dac7c"; // if we need to pass the token obtained from the username/password auth if (credential.Contains(":")) { webRequestPersonalAccessTokens.Headers.Add("Authorization", "Bearer " + accessToken); } // if user just specified personal access token else { webRequestPersonalAccessTokens.Headers.Add("PRIVATE-TOKEN", credential); } // get web response WebResponse myWebResponsePersonalAccessToken = await webRequestPersonalAccessTokens.GetResponseAsync(); string contentPersonalAccessToken; var readerPersonalAccessToken = new StreamReader(myWebResponsePersonalAccessToken.GetResponseStream()); contentPersonalAccessToken = readerPersonalAccessToken.ReadToEnd(); // if more than 1 page, we need to track what next URL will be string nextURL = ""; // parse the JSON output and display result to retrieve the PAT List patResults = JsonConvert.DeserializeObject>(contentPersonalAccessToken); foreach (patResult item in patResults) { string scopesToList = ""; foreach (string a in item.scopes) { scopesToList += a + ", "; } scopesToList = scopesToList.Substring(0, scopesToList.Length - 2); // only display an active PAT if (item.active == true) { Console.WriteLine("{0,5} | {1,20} | {2,10} | {3,50}", item.id, item.name, item.active, scopesToList); } } // figure out if there are more results due to paging and we need to send another request for the next page for (int i = 0; i < myWebResponsePersonalAccessToken.Headers.Count; ++i) { string[] splitValues = myWebResponsePersonalAccessToken.Headers[i].Split(','); foreach (string val in splitValues) { if (val.Contains("rel=\"next\"")) { nextURL = val; nextURL = nextURL.Replace(">; rel=\"next\"", ""); nextURL = nextURL.Replace("<", ""); } } } // make subsequent requests if more than 1 page if (!nextURL.Equals("")) { await makeSubsequentRequestAsync(credential, nextURL, options, accessToken); } } } catch (KeyNotFoundException ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: User provided does not exist to list personal acccess tokens. Exception: " + ex.ToString()); Console.WriteLine(""); } catch (Exception ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Could not list personal access token for user. Exception: " + ex.ToString()); Console.WriteLine(""); } } // this is just placeholder for making more than 1 request due to paging of results. will have better global solution at some point than this band-aid public static async Task makeSubsequentRequestAsync(string credential, string url, string options, string token) { try { string accessToken = token; // get personal access tokens for user var webRequestPersonalAccessTokens = (HttpWebRequest)System.Net.WebRequest.Create(url); if (webRequestPersonalAccessTokens != null) { // set header values webRequestPersonalAccessTokens.Method = "GET"; webRequestPersonalAccessTokens.ContentType = "application/json"; webRequestPersonalAccessTokens.UserAgent = "SCMKIT-5dc493ada400c79dd318abbe770dac7c"; // if we need to pass the token obtained from the username/password auth if (credential.Contains(":")) { webRequestPersonalAccessTokens.Headers.Add("Authorization", "Bearer " + accessToken); } // if user just specified personal access token else { webRequestPersonalAccessTokens.Headers.Add("PRIVATE-TOKEN", credential); } // get web response WebResponse myWebResponsePersonalAccessToken = await webRequestPersonalAccessTokens.GetResponseAsync(); string contentPersonalAccessToken; var readerPersonalAccessToken = new StreamReader(myWebResponsePersonalAccessToken.GetResponseStream()); contentPersonalAccessToken = readerPersonalAccessToken.ReadToEnd(); string nextURL = ""; // parse the JSON output and display result to retrieve the personal access token id(s) List patResults = JsonConvert.DeserializeObject>(contentPersonalAccessToken); foreach (patResult item in patResults) { string scopesToList = ""; foreach (string a in item.scopes) { scopesToList += a + ", "; } scopesToList = scopesToList.Substring(0, scopesToList.Length - 2); // only display an active PAT if (item.active == true) { Console.WriteLine("{0,5} | {1,20} | {2,10} | {3,50}", item.id, item.name, item.active, scopesToList); } } // figure out if there are more results due to paging and we need to send another request for the next page for (int i = 0; i < myWebResponsePersonalAccessToken.Headers.Count; ++i) { string[] splitValues = myWebResponsePersonalAccessToken.Headers[i].Split(','); foreach (string val in splitValues) { if (val.Contains("rel=\"next\"")) { nextURL = val; nextURL = nextURL.Replace(">; rel=\"next\"", ""); nextURL = nextURL.Substring(1, nextURL.Length - 1); nextURL = nextURL.Replace("<", ""); } } } // make subsequent requests if more pages if (!nextURL.Equals("")) { await makeSubsequentRequestAsync(credential, nextURL, options, accessToken); } } } catch (KeyNotFoundException ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: User provided does not exist to list personal acccess tokens. Exception: " + ex.ToString()); Console.WriteLine(""); } catch (Exception ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Could not list personal access token for user. Exception: " + ex.ToString()); Console.WriteLine(""); } } } } ================================================ FILE: SCMKit/modules/gitlab/ListSSHKeys.cs ================================================ using System; using System.Collections.Generic; using System.Net; using System.Net.Security; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; using System.IO; using System.Linq; namespace SCMKit.modules.gitlab { class ListSSHKeys { public static async Task execute(string credential, string url, string options, string system) { // Generate module header Console.WriteLine(library.Utils.GenerateHeader("listsshkey", credential, url, options, system)); // dictionary to hold lookup table user ID's and usernames Dictionary userMapping = new Dictionary(); try { await library.Utils.HeartbeatRequest(url); // create table header string tableHeader = string.Format("{0,12} | {1,25} | {2,20}", "SSH Key ID", "SSH Key Value", "Title"); Console.WriteLine(tableHeader); Console.WriteLine(new String('-', tableHeader.Length)); ServicePointManager.ServerCertificateValidationCallback = delegate (object s, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { return true; }; ServicePointManager.Expect100Continue = true; ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; // proceed with request to list ssh keys for given user string accessToken = ""; // if username/password auth being used, get the access token first. this is needed for subsequent API requests if (credential.Contains(":")) { accessToken = library.GitLabUtils.GetAccessToken(credential, url); } // perform request to list SSH keys var webRequest = (HttpWebRequest)System.Net.WebRequest.Create(url + "/api/v4/user/keys"); if (webRequest != null) { // set header values webRequest.Method = "GET"; webRequest.ContentType = "application/json"; webRequest.UserAgent = "SCMKIT-5dc493ada400c79dd318abbe770dac7c"; // if we need to pass the token obtained from the username/password auth if (credential.Contains(":")) { webRequest.Headers.Add("Authorization", "Bearer " + accessToken); } // if user just specified personal access token else { webRequest.Headers.Add("PRIVATE-TOKEN", credential); } // get the response and the access token var httpResponse = (HttpWebResponse)webRequest.GetResponse(); var streamReader = new StreamReader(httpResponse.GetResponseStream()); string result = streamReader.ReadToEnd(); // get all instances of the SSH key id IEnumerable startingIndexesID = library.Utils.AllIndexesOf(result, "\"id\":"); IEnumerable endingIndexesID = library.Utils.AllIndexesOf(result, "\"title\":"); List listOfIndexIDs = new List(); for (int i = 0; i < startingIndexesID.Count(); i++) { string sshKeyID = ""; sshKeyID = result.Substring(startingIndexesID.ElementAt(i) + "\"id\":".Length, endingIndexesID.ElementAt(i) - startingIndexesID.ElementAt(i) - "\"id\"".Length); sshKeyID = sshKeyID.Replace("\"", ""); sshKeyID = sshKeyID.Replace(",", ""); listOfIndexIDs.Add(sshKeyID); } // get all instances of the SSH key value IEnumerable startingIndexesValue = library.Utils.AllIndexesOf(result, "\"key\":"); IEnumerable endingIndexesValue = library.Utils.AllIndexesOf(result, "\"}"); List listOfIndexValues = new List(); for (int i = 0; i < startingIndexesValue.Count(); i++) { string sshKeyValue = ""; sshKeyValue = result.Substring(startingIndexesValue.ElementAt(i) + "\"key\":".Length, endingIndexesValue.ElementAt(i) - startingIndexesValue.ElementAt(i) - "\"key\"".Length); sshKeyValue = sshKeyValue.Replace("\"", ""); sshKeyValue = sshKeyValue.Replace(",", ""); listOfIndexValues.Add(sshKeyValue); } // get all instances of the SSH key title IEnumerable startingIndexesTitle = library.Utils.AllIndexesOf(result, "\"title\":"); IEnumerable endingIndexesTitle = library.Utils.AllIndexesOf(result, "\"created_at\":"); List listOfIndexTitles = new List(); for (int i = 0; i < startingIndexesTitle.Count(); i++) { string sshKeyTitle = ""; sshKeyTitle = result.Substring(startingIndexesTitle.ElementAt(i) + "\"title\":".Length, endingIndexesTitle.ElementAt(i) - startingIndexesTitle.ElementAt(i) - "\"ketitley\"".Length); sshKeyTitle = sshKeyTitle.Replace("\"", ""); sshKeyTitle = sshKeyTitle.Replace(",", ""); listOfIndexTitles.Add(sshKeyTitle); } // iterate through and print the ssh key ID's and contents for (int i = 0; i < listOfIndexIDs.Count(); i++) { string[] sshKeyArray = listOfIndexValues[i].Split(' '); string justSSHKey = sshKeyArray[1].Substring(sshKeyArray[1].Length - 20, 20); Console.WriteLine("{0,12} | {1,25} | {2,20}", listOfIndexIDs[i], "....." + justSSHKey, listOfIndexTitles[i]); } } } catch (Exception ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Could not list SSH keys for user. Exception: " + ex.ToString()); Console.WriteLine(""); } } } } ================================================ FILE: SCMKit/modules/gitlab/Privs.cs ================================================ using System; using System.Collections.Generic; using System.Net; using System.Net.Security; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; using System.IO; using Newtonsoft.Json; namespace SCMKit.modules.gitlab { // custom class to handle the JSON output of API token permissions public class apiPermissions { public string id { get; set; } public string name { get; set; } public bool revoked { get; set; } public string createdAt { get; set; } public string[] scopes { get; set; } public string user_id { get; set; } public bool active { get; set; } public string expiresAt { get; set; } public apiPermissions() { } } class Privs { public static async Task execute(string credential, string url, string options, string system) { Dictionary valuePairs = new Dictionary(); valuePairs.Add("api", "Read-write for the complete API, including all groups and projects, the Container Registry, and the Package Registry."); valuePairs.Add("read_user", "Read-only for endpoints under /users. Essentially, access to any of the GET requests in the Users API."); valuePairs.Add("read_api", "Read-only for the complete API, including all groups and projects, the Container Registry, and the Package Registry."); valuePairs.Add("read_repository", " Read-only (pull) for the repository through git clone."); valuePairs.Add("write_repository", "Read-write (pull, push) for the repository through git clone. Required for accessing Git repositories over HTTP when 2FA is enabled."); valuePairs.Add("read_registry", "Read-only (pull) for Container Registry images if a project is private and authorization is required."); valuePairs.Add("write_registry", "Read-write (push) for Container Registry images if a project is private and authorization is required."); valuePairs.Add("sudo", "API actions as any user in the system (if the authenticated user is an administrator)."); // Generate module header Console.WriteLine(library.Utils.GenerateHeader("privs", credential, url, options, system)); List listOfPermissions = new List(); // if username/password auth being used if (credential.Contains(":")) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Privs module only supports API key authentication to determine privs of the API key given."); Console.WriteLine(""); } // if token auth being used else { try { ServicePointManager.ServerCertificateValidationCallback = delegate (object s, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { return true; }; ServicePointManager.Expect100Continue = true; ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; await library.Utils.HeartbeatRequest(url); // get the users id to be passed in subsequent request string userId = ""; var webRequestUserID = (HttpWebRequest)System.Net.WebRequest.Create(url + "/api/v4/user"); if (webRequestUserID != null) { // set header values webRequestUserID.Method = "GET"; webRequestUserID.ContentType = "application/json"; webRequestUserID.Headers.Add("PRIVATE-TOKEN", credential); webRequestUserID.UserAgent = "SCMKIT-5dc493ada400c79dd318abbe770dac7c"; // get web response WebResponse myWebResponseUserID = await webRequestUserID.GetResponseAsync(); string contentUserID; var readerUserID = new StreamReader(myWebResponseUserID.GetResponseStream()); contentUserID = readerUserID.ReadToEnd(); int startIndex = contentUserID.IndexOf("\"id\":"); int endIndex = contentUserID.IndexOf("\"name\":"); userId = contentUserID.Substring(startIndex + "\"id\":".Length, endIndex - startIndex - "\"name\"".Length); myWebResponseUserID.Close(); } // create table header string tableHeader = string.Format("{0,20} | {1,10} | {2,20} | {3,70}", "Token Name", "Active?", "Privilege", "Description"); Console.WriteLine(tableHeader); Console.WriteLine(new String('-', tableHeader.Length)); // get personal access tokens for user var webRequest = (HttpWebRequest)System.Net.WebRequest.Create(url + "/api/v4/personal_access_tokens"); if (webRequest != null) { // set header values webRequest.Method = "GET"; webRequest.ContentType = "application/json"; webRequest.Headers.Add("PRIVATE-TOKEN", credential); webRequest.UserAgent = "SCMKIT-5dc493ada400c79dd318abbe770dac7c"; // get web response WebResponse myWebResponse = await webRequest.GetResponseAsync(); string content; var reader = new StreamReader(myWebResponse.GetResponseStream()); content = reader.ReadToEnd(); // if more than 1 page, we need to track what next URL will be string nextURL = ""; // parse the JSON output and display results List apiPerms = JsonConvert.DeserializeObject>(content); foreach (apiPermissions item in apiPerms) { string keyName = item.name; string uid = item.user_id; // if user is not filtering and just wants to see there tokens if (options.Equals("") && uid.Equals(userId)) { if (uid.Equals(userId)) { string[] theScopes = item.scopes; for (int i = 0; i < theScopes.Length; i++) { string theKeyScope = theScopes[i]; // only display an active PAT if (item.active == true) { Console.WriteLine("{0,20} | {1,10} | {2,20} | {3,70}", item.name, item.active, theKeyScope, valuePairs[theKeyScope]); } } } } // if user is filtering on specific PAT if (!options.Equals("")) { string[] theScopes = item.scopes; for (int i = 0; i < theScopes.Length; i++) { string theKeyScope = theScopes[i]; // if user is filtering for specific PAT if (!options.Equals("")) { if (options.ToLower().Equals(keyName.ToLower())) { // only display an active PAT if (item.active == true) { Console.WriteLine("{0,20} | {1,10} | {2,20} | {3,70}", item.name, item.active, theKeyScope, valuePairs[theKeyScope]); } } } // if user just wants to see all PATs it can see else { // only display an active PAT if (item.active == true) { Console.WriteLine("{0,20} | {1,10} | {2,20} | {3,70}", item.name, item.active, theKeyScope, valuePairs[theKeyScope]); } } } } // figure out if there are more results due to paging and we need to send another request for the next page for (int i = 0; i < myWebResponse.Headers.Count; ++i) { string[] splitValues = myWebResponse.Headers[i].Split(','); foreach (string val in splitValues) { if (val.Contains("rel=\"next\"")) { nextURL = val; nextURL = nextURL.Replace(">; rel=\"next\"", ""); nextURL = nextURL.Substring(1, nextURL.Length - 1); } } } // make subsequent requests if more than 1 page if (!nextURL.Equals("")) { await makeSubsequentRequestAsync(credential, nextURL, options, userId, valuePairs); } } } } catch (Exception ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Could not retrieve listing of privileges for current API token. Exception: " + ex.ToString()); Console.WriteLine(""); } } } // this is just placeholder for making more than 1 request due to paging of results. will have better global solution at some point than this band-aid public static async Task makeSubsequentRequestAsync(string credential, string url, string options, string userId, Dictionary valuePairs) { try { string accessToken = ""; // if username/password auth being used, get the access token first. this is needed for subsequent API requests if (credential.Contains(":")) { accessToken = library.GitLabUtils.GetAccessToken(credential, url); } // get personal access tokens for user var webRequest = (HttpWebRequest)System.Net.WebRequest.Create(url); if (webRequest != null) { // set header values webRequest.Method = "GET"; webRequest.ContentType = "application/json"; webRequest.Headers.Add("PRIVATE-TOKEN", credential); webRequest.UserAgent = "SCMKIT-5dc493ada400c79dd318abbe770dac7c"; // get web response WebResponse myWebResponse = await webRequest.GetResponseAsync(); string content; var reader = new StreamReader(myWebResponse.GetResponseStream()); content = reader.ReadToEnd(); string nextURL = ""; // parse the JSON output and display results List apiPerms = JsonConvert.DeserializeObject>(content); foreach (apiPermissions item in apiPerms) { string keyName = item.name; string uid = item.user_id; // if user is not filtering and just wants to see there tokens if (options.Equals("") && uid.Equals(userId)) { if (uid.Equals(userId)) { string[] theScopes = item.scopes; for (int i = 0; i < theScopes.Length; i++) { string theKeyScope = theScopes[i]; // only display an active PAT if (item.active == true) { Console.WriteLine("{0,20} | {1,10} | {2,20} | {3,70}", item.name, item.active, theKeyScope, valuePairs[theKeyScope]); } } } } // if user is filtering on specific PAT if (!options.Equals("")) { string[] theScopes = item.scopes; for (int i = 0; i < theScopes.Length; i++) { string theKeyScope = theScopes[i]; // if user is filtering for specific PAT if (!options.Equals("")) { if (options.ToLower().Equals(keyName.ToLower())) { // only display an active PAT if (item.active == true) { Console.WriteLine("{0,20} | {1,10} | {2,20} | {3,70}", item.name, item.active, theKeyScope, valuePairs[theKeyScope]); } } } // if user just wants to see all PATs it can see else { // only display an active PAT if (item.active == true) { Console.WriteLine("{0,20} | {1,10} | {2,20} | {3,70}", item.name, item.active, theKeyScope, valuePairs[theKeyScope]); } } } // figure out if there are more results due to paging and we need to send another request for the next page for (int i = 0; i < myWebResponse.Headers.Count; ++i) { string[] splitValues = myWebResponse.Headers[i].Split(','); foreach (string val in splitValues) { if (val.Contains("rel=\"next\"")) { nextURL = val; nextURL = nextURL.Replace(">; rel=\"next\"", ""); nextURL = nextURL.Substring(1, nextURL.Length - 1); nextURL = nextURL.Replace("<", ""); } } } // make subsequent requests if more pages if (!nextURL.Equals("")) { await makeSubsequentRequestAsync(credential, nextURL, options, userId, valuePairs); } } } } } catch (Exception ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Could not retrieve listing of privileges for current API token. Exception: " + ex.ToString()); Console.WriteLine(""); } } } } ================================================ FILE: SCMKit/modules/gitlab/RemoveAdmin.cs ================================================ using System; using System.Collections.Generic; using System.Net; using System.Net.Security; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; using System.IO; using GitLabApiClient; namespace SCMKit.modules.gitlab { class RemoveAdmin { public static async Task execute(string credential, string url, string options, string system) { // Generate module header Console.WriteLine(library.Utils.GenerateHeader("removeadmin", credential, url, options, system)); // dicationary to hold lookup table user ID's and usernames Dictionary userMapping = new Dictionary(); try { // auth to GitLab and get list of all users Task authTask = library.GitLabUtils.AuthToGitLabAsync(credential, url); GitLabClient client = authTask.Result; var users = await client.Users.GetAsync(); // add associated user and user id to the dictionary for subsequent requests foreach (var user in users) { userMapping.Add(user.Username, user.Id.ToString()); } ServicePointManager.ServerCertificateValidationCallback = delegate (object s, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { return true; }; ServicePointManager.Expect100Continue = true; ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; await library.Utils.HeartbeatRequest(url); // proceed with request to remove admin permissions for the user given string accessToken = ""; // if username/password auth being used, get the access token first. this is needed for subsequent API requests if (credential.Contains(":")) { accessToken = library.GitLabUtils.GetAccessToken(credential, url); } string theUserID = userMapping[options.ToLower()]; // get snippets for user var webRequest = (HttpWebRequest)System.Net.WebRequest.Create(url + "/api/v4/users/" + theUserID); if (webRequest != null) { // set header values webRequest.Method = "PUT"; webRequest.ContentType = "application/json"; webRequest.UserAgent = "SCMKIT-5dc493ada400c79dd318abbe770dac7c"; // if we need to pass the token obtained from the username/password auth if (credential.Contains(":")) { webRequest.Headers.Add("Authorization", "Bearer " + accessToken); } // if user just specified personal access token else { webRequest.Headers.Add("PRIVATE-TOKEN", credential); } // set body and sent request using (var streamWriter = new StreamWriter(webRequest.GetRequestStream())) { string json = "{\"admin\":\"false\"}"; streamWriter.Write(json); } // get the response and the access token var httpResponse = (HttpWebResponse)webRequest.GetResponse(); var streamReader = new StreamReader(httpResponse.GetResponseStream()); string result = streamReader.ReadToEnd(); Console.WriteLine(""); Console.WriteLine("[+] SUCCESS: The " + options + " user was successfully removed from the admin role."); Console.WriteLine(""); } } catch (KeyNotFoundException ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: User provided does not exist to remove admin role. Exception: " + ex.ToString()); Console.WriteLine(""); } catch (Exception ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Could not remove user from admin role. Exception: " + ex.ToString()); Console.WriteLine(""); } } } } ================================================ FILE: SCMKit/modules/gitlab/RemovePAT.cs ================================================ using System; using System.Collections.Generic; using System.Net; using System.Net.Security; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; using System.IO; namespace SCMKit.modules.gitlab { class RemovePAT { public static async Task execute(string credential, string url, string options, string system) { // Generate module header Console.WriteLine(library.Utils.GenerateHeader("removepat", credential, url, options, system)); try { ServicePointManager.ServerCertificateValidationCallback = delegate (object s, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { return true; }; ServicePointManager.Expect100Continue = true; ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; await library.Utils.HeartbeatRequest(url); // proceed with request to remove PAT for the user given string accessToken = ""; // if username/password auth being used, get the access token first. this is needed for subsequent API requests if (credential.Contains(":")) { accessToken = library.GitLabUtils.GetAccessToken(credential, url); } // if user didn't specify an ID, display message and return if (options.Equals("")) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Must supply ID of PAT to remove."); Console.WriteLine(""); return; } Console.WriteLine(""); Console.WriteLine(""); Console.WriteLine("[*] INFO: Revoking personal access token of ID: " + options); Console.WriteLine(""); // perform request to remove PAT var webRequest = (HttpWebRequest)System.Net.WebRequest.Create(url + "/api/v4/personal_access_tokens/" + options); if (webRequest != null) { // set header values webRequest.Method = "DELETE"; webRequest.ContentType = "application/x-www-form-urlencoded"; webRequest.UserAgent = "SCMKIT-5dc493ada400c79dd318abbe770dac7c"; // if we need to pass the token obtained from the username/password auth if (credential.Contains(":")) { webRequest.Headers.Add("Authorization", "Bearer " + accessToken); } // if user just specified personal access token else { webRequest.Headers.Add("PRIVATE-TOKEN", credential); } // set body and sent request using (var streamWriter = new StreamWriter(webRequest.GetRequestStream())) { string body = ""; streamWriter.Write(body); } // get the response and the access token var httpResponse = (HttpWebResponse)webRequest.GetResponse(); var streamReader = new StreamReader(httpResponse.GetResponseStream()); string result = streamReader.ReadToEnd(); Console.WriteLine(""); Console.WriteLine("[+] SUCCESS: The personal access token of ID " + options + " was successfully revoked."); Console.WriteLine(""); } } catch (KeyNotFoundException ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: User provided does not exist to revoke personal access token. Exception: " + ex.ToString()); Console.WriteLine(""); } catch (Exception ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Could not revoke personal access token for user. Exception: " + ex.ToString()); Console.WriteLine(""); } } } } ================================================ FILE: SCMKit/modules/gitlab/RemoveSSHKey.cs ================================================ using System; using System.Collections.Generic; using System.Net; using System.Net.Security; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; using System.IO; namespace SCMKit.modules.gitlab { class RemoveSSHKey { public static async Task execute(string credential, string url, string options, string system) { // Generate module header Console.WriteLine(library.Utils.GenerateHeader("removesshkey", credential, url, options, system)); // dictionary to hold lookup table user ID's and usernames Dictionary userMapping = new Dictionary(); try { await library.Utils.HeartbeatRequest(url); ServicePointManager.ServerCertificateValidationCallback = delegate (object s, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { return true; }; ServicePointManager.Expect100Continue = true; ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; // proceed with request to remove ssh key for given user string accessToken = ""; // if username/password auth being used, get the access token first. this is needed for subsequent API requests if (credential.Contains(":")) { accessToken = library.GitLabUtils.GetAccessToken(credential, url); } // if user didn't specify an ID, display message and return if (options.Equals("")) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Must supply ID of SSH key to remove."); Console.WriteLine(""); return; } // perform request to remove SSH key var webRequest = (HttpWebRequest)System.Net.WebRequest.Create(url + "/api/v4/user/keys/" + options); if (webRequest != null) { // set header values webRequest.Method = "DELETE"; webRequest.ContentType = "application/json"; webRequest.UserAgent = "SCMKIT-5dc493ada400c79dd318abbe770dac7c"; // if we need to pass the token obtained from the username/password auth if (credential.Contains(":")) { webRequest.Headers.Add("Authorization", "Bearer " + accessToken); } // if user just specified personal access token else { webRequest.Headers.Add("PRIVATE-TOKEN", credential); } // get the response and the access token var httpResponse = (HttpWebResponse)webRequest.GetResponse(); var streamReader = new StreamReader(httpResponse.GetResponseStream()); string result = streamReader.ReadToEnd(); // if we got 204 response, SSH key was removed if (httpResponse.StatusCode.ToString().ToLower().Equals("nocontent")) { Console.WriteLine(""); Console.WriteLine("[+] SUCCESS: The SSH key of ID " + options + " was successfully revoked."); Console.WriteLine(""); } else { Console.WriteLine(""); Console.WriteLine("[-] ERROR: There was an error revoking the SSH key."); Console.WriteLine(""); } } } catch (KeyNotFoundException ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: User provided does not exist to remove SSH key for. Exception: " + ex.ToString()); Console.WriteLine(""); } catch (Exception ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Could not remove SSH key for user. Exception: " + ex.ToString()); Console.WriteLine(""); } } } } ================================================ FILE: SCMKit/modules/gitlab/RepoList.cs ================================================ using System; using System.Threading.Tasks; using GitLabApiClient; namespace SCMKit.modules.gitlab { class RepoList { public static async Task execute(string credential, string url, string options, string system) { // Generate module header Console.WriteLine(library.Utils.GenerateHeader("listrepo", credential, url, options, system)); try { await library.Utils.HeartbeatRequest(url); // auth to GitLab and get list of all projects Task authTask = library.GitLabUtils.AuthToGitLabAsync(credential, url); GitLabClient client = authTask.Result; var projects = await client.Projects.GetAsync(); // create table header string tableHeader = string.Format("{0,40} | {1,10} | {2,50}", "Name", "Visibility", "URL"); Console.WriteLine(tableHeader); Console.WriteLine(new String('-', tableHeader.Length)); foreach (var project in projects) { if (options.ToLower().Equals("private") && project.Visibility.ToString().Equals("Private")) { Console.WriteLine("{0,40} | {1,10} | {2,50}", project.Name, project.Visibility, project.WebUrl); } else if (options.ToLower().Equals("public") && project.Visibility.ToString().Equals("Public")) { Console.WriteLine("{0,40} | {1,10} | {2,50}", project.Name, project.Visibility, project.WebUrl); } else if (options.ToLower().Equals("internal") && project.Visibility.ToString().Equals("Internal")) { Console.WriteLine("{0,40} | {1,10} | {2,50}", project.Name, project.Visibility, project.WebUrl); } else if (options.Equals("")) { Console.WriteLine("{0,40} | {1,10} | {2,50}", project.Name, project.Visibility, project.WebUrl); } } } catch (Exception ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Could not retrieve listing of repos. Exception: " + ex.ToString()); Console.WriteLine(""); } } } } ================================================ FILE: SCMKit/modules/gitlab/RepoSearch.cs ================================================ using System; using System.Collections.Generic; using System.Net; using System.Net.Security; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; using System.IO; using System.Linq; namespace SCMKit.modules.gitlab { class RepoSearch { private static int searchMatchCount = 0; public static async Task execute(string credential, string url, string options, string system) { // Generate module header Console.WriteLine(library.Utils.GenerateHeader("searchrepo", credential, url, options, system)); try { await library.Utils.HeartbeatRequest(url); // create table header string tableHeader = string.Format("{0,40} | {1,10} | {2,50}", "Name", "Visibility", "URL"); Console.WriteLine(tableHeader); Console.WriteLine(new String('-', tableHeader.Length)); ServicePointManager.ServerCertificateValidationCallback = delegate (object s, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { return true; }; ServicePointManager.Expect100Continue = true; ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; string accessToken = ""; // if username/password auth being used, get the access token first. this is needed for subsequent API requests if (credential.Contains(":")) { accessToken = library.GitLabUtils.GetAccessToken(credential, url); } // proceed with repo search var webRequest = (HttpWebRequest)System.Net.WebRequest.Create(url + "/api/v4/search?scope=projects&search=" + options); if (webRequest != null) { // set header values webRequest.Method = "GET"; webRequest.ContentType = "application/json"; webRequest.UserAgent = "SCMKIT-5dc493ada400c79dd318abbe770dac7c"; // if we need to pass the token obtained from the username/password auth if (credential.Contains(":")) { webRequest.Headers.Add("Authorization", "Bearer " + accessToken); } // if user just specified personal access token else { webRequest.Headers.Add("PRIVATE-TOKEN", credential); } // get web response WebResponse myWebResponse = await webRequest.GetResponseAsync(); string result; var reader = new StreamReader(myWebResponse.GetResponseStream()); result = reader.ReadToEnd(); // get all instances of the id IEnumerable startingIndexesID = AllIndexesOf(result, "[{\"id\":", ",{\"id\":"); IEnumerable endingIndexesID = library.Utils.AllIndexesOf(result, "\"description\":"); List listOfIDs = new List(); for (int i = 0; i < startingIndexesID.Count(); i++) { string theID = ""; theID = result.Substring(startingIndexesID.ElementAt(i) + "[{\"id\":".Length, endingIndexesID.ElementAt(i) - startingIndexesID.ElementAt(i) - "[{\"id\":".Length); theID = theID.Replace(",", ""); theID = theID.Replace(" ", ""); listOfIDs.Add(theID); } // get all instances of the name IEnumerable startingIndexesName = library.Utils.AllIndexesOf(result, "\"name_with_namespace\":"); IEnumerable endingIndexesName = library.Utils.AllIndexesOf(result, "\"path_with_namespace\":"); List listOfNames = new List(); for (int i = 0; i < startingIndexesName.Count(); i++) { string theName = ""; theName = result.Substring(startingIndexesName.ElementAt(i) + "\"name_with_namespace\":".Length, endingIndexesName.ElementAt(i) - startingIndexesName.ElementAt(i) - "\"name_with_namespace\":".Length); string[] splitNameArray = theName.Split(','); theName = splitNameArray[0]; theName = theName.Replace("\"", ""); string[] splitAgain = theName.Split('/'); theName = splitAgain[1]; theName.TrimStart(); listOfNames.Add(theName); } // get all instances of the URL IEnumerable startingIndexesURL = library.Utils.AllIndexesOf(result, "\"http_url_to_repo\":"); IEnumerable endingIndexesURL = library.Utils.AllIndexesOf(result, "\"readme_url\":"); List listOfURLs = new List(); for (int i = 0; i < startingIndexesURL.Count(); i++) { string theURL = ""; theURL = result.Substring(startingIndexesURL.ElementAt(i) + "\"http_url_to_repo\":".Length, endingIndexesURL.ElementAt(i) - startingIndexesURL.ElementAt(i) - "\"http_url_to_repo\":".Length); string[] splitURLArray = theURL.Split(','); theURL = splitURLArray[0]; theURL = theURL.Replace("\"", ""); listOfURLs.Add(theURL); } // print the results for (int i = 0; i < listOfIDs.Count; i++) { string visibility = await library.GitLabUtils.GetGitLabProjectVisibility(credential, accessToken, listOfIDs[i], url); Console.WriteLine("{0,40} | {1,10} | {2,50}", listOfNames[i], visibility, listOfURLs[i]); searchMatchCount++; } string nextPage = ""; // determine if there are more results and subsequent requests need made for (int i = 0; i < myWebResponse.Headers.Count; ++i) { if (myWebResponse.Headers.Keys[i].ToString().ToLower().Equals("x-next-page") && myWebResponse.Headers.GetValues(i).Length == 1) { nextPage = myWebResponse.Headers.GetValues(i)[0]; } } // if there are more pages, then make subsequent requests if (!nextPage.Equals("")) { await makeSubsequentRequestAsync(credential, url, options, accessToken, nextPage); } } Console.WriteLine(""); Console.WriteLine("Total number of items matching repo search: " + searchMatchCount); } catch (Exception ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Could not perform repo search for search term given. Exception: " + ex.ToString()); Console.WriteLine(""); } } /** * method to get all indexes where a value exists in a string. this one is specialized for ID field of GitLab parsing * * */ public static int[] AllIndexesOf(string str, string substrOne, string substrTwo, bool ignoreCase = false) { if (string.IsNullOrWhiteSpace(str) || string.IsNullOrWhiteSpace(substrOne) || string.IsNullOrWhiteSpace(substrTwo)) { throw new ArgumentException("String or substring is not specified."); } var indexes = new List(); int index = 0; while ((index = str.IndexOf(substrOne, index, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal)) != -1) { indexes.Add(index++); } int indexTwo = 0; while ((indexTwo = str.IndexOf(substrTwo, indexTwo, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal)) != -1) { indexes.Add(indexTwo++); } return indexes.ToArray(); } // this is just placeholder for making more than 1 request due to paging of results. will have better global solution at some point than this band-aid public static async Task makeSubsequentRequestAsync(string credential, string url, string options, string accessToken, string nextPage) { try { ServicePointManager.ServerCertificateValidationCallback = delegate (object s, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { return true; }; ServicePointManager.Expect100Continue = true; ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; // proceed with repo search var webRequest = (HttpWebRequest)System.Net.WebRequest.Create(url + "/api/v4/search?scope=projects&page=" + nextPage + "&search=" + options); if (webRequest != null) { // set header values webRequest.Method = "GET"; webRequest.ContentType = "application/json"; webRequest.UserAgent = "SCMKIT-5dc493ada400c79dd318abbe770dac7c"; // if we need to pass the token obtained from the username/password auth if (credential.Contains(":")) { webRequest.Headers.Add("Authorization", "Bearer " + accessToken); } // if user just specified personal access token else { webRequest.Headers.Add("PRIVATE-TOKEN", credential); } // get web response WebResponse myWebResponse = await webRequest.GetResponseAsync(); string result; var reader = new StreamReader(myWebResponse.GetResponseStream()); result = reader.ReadToEnd(); // get all instances of the id IEnumerable startingIndexesID = AllIndexesOf(result, "[{\"id\":", ",{\"id\":"); IEnumerable endingIndexesID = library.Utils.AllIndexesOf(result, "\"description\":"); List listOfIDs = new List(); for (int i = 0; i < startingIndexesID.Count(); i++) { string theID = ""; theID = result.Substring(startingIndexesID.ElementAt(i) + "[{\"id\":".Length, endingIndexesID.ElementAt(i) - startingIndexesID.ElementAt(i) - "[{\"id\":".Length); theID = theID.Replace(",", ""); theID = theID.Replace(" ", ""); listOfIDs.Add(theID); } // get all instances of the name IEnumerable startingIndexesName = library.Utils.AllIndexesOf(result, "\"name_with_namespace\":"); IEnumerable endingIndexesName = library.Utils.AllIndexesOf(result, "\"path_with_namespace\":"); List listOfNames = new List(); for (int i = 0; i < startingIndexesName.Count(); i++) { string theName = ""; theName = result.Substring(startingIndexesName.ElementAt(i) + "\"name_with_namespace\":".Length, endingIndexesName.ElementAt(i) - startingIndexesName.ElementAt(i) - "\"name_with_namespace\":".Length); string[] splitNameArray = theName.Split(','); theName = splitNameArray[0]; theName = theName.Replace("\"", ""); string[] splitAgain = theName.Split('/'); theName = splitAgain[1]; theName.TrimStart(); listOfNames.Add(theName); } // get all instances of the URL IEnumerable startingIndexesURL = library.Utils.AllIndexesOf(result, "\"http_url_to_repo\":"); IEnumerable endingIndexesURL = library.Utils.AllIndexesOf(result, "\"readme_url\":"); List listOfURLs = new List(); for (int i = 0; i < startingIndexesURL.Count(); i++) { string theURL = ""; theURL = result.Substring(startingIndexesURL.ElementAt(i) + "\"http_url_to_repo\":".Length, endingIndexesURL.ElementAt(i) - startingIndexesURL.ElementAt(i) - "\"http_url_to_repo\":".Length); string[] splitURLArray = theURL.Split(','); theURL = splitURLArray[0]; theURL = theURL.Replace("\"", ""); listOfURLs.Add(theURL); } // print the results for (int i = 0; i < listOfIDs.Count; i++) { string visibility = await library.GitLabUtils.GetGitLabProjectVisibility(credential, accessToken, listOfIDs[i], url); Console.WriteLine("{0,40} | {1,10} | {2,50}", listOfNames[i], visibility, listOfURLs[i]); searchMatchCount++; } // determine if there are more results and subsequent requests need made for (int i = 0; i < myWebResponse.Headers.Count; ++i) { if (myWebResponse.Headers.Keys[i].ToString().ToLower().Equals("x-next-page") && myWebResponse.Headers.GetValues(i).Length == 1) { nextPage = myWebResponse.Headers.GetValues(i)[0]; } } // if there are more pages, then make subsequent requests if (!nextPage.Equals("")) { await makeSubsequentRequestAsync(credential, url, options, accessToken, nextPage); } } } catch (Exception ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Could not perform repo search. Exception: " + ex.ToString()); Console.WriteLine(""); } } } } ================================================ FILE: SCMKit/modules/gitlab/RunnerList.cs ================================================ using System; using System.Collections.Generic; using System.Net; using System.Net.Security; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; using System.IO; using System.Linq; namespace SCMKit.modules.gitlab { // custom class to store a runner public class aRunner { public string id { get; set; } public string name { get; set; } public string project { get; set; } public aRunner(string id, string name, string project) { this.id = id; this.name = name; this.project = project; } } class RunnerList { private static List theRunners = new List(); private static List nameAndIDs = new List(); public static async Task execute(string credential, string url, string options, string system) { // Generate module header Console.WriteLine(library.Utils.GenerateHeader("listrunner", credential, url, options, system)); try { await library.Utils.HeartbeatRequest(url); ServicePointManager.ServerCertificateValidationCallback = delegate (object s, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { return true; }; ServicePointManager.Expect100Continue = true; ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; // proceed with request to list runners accessible for a given user string accessToken = ""; // if username/password auth being used, get the access token first. this is needed for subsequent API requests if (credential.Contains(":")) { accessToken = library.GitLabUtils.GetAccessToken(credential, url); } // create table header string tableHeader = string.Format("{0,5} | {1,20} | {2,50}", "ID", "Name", "Repo Assigned"); Console.WriteLine(tableHeader); Console.WriteLine(new String('-', tableHeader.Length)); // perform request to get list of runners as we will need the ID of the runners to get more details var webRequest = (HttpWebRequest)System.Net.WebRequest.Create(url + "/api/v4/runners/all"); if (webRequest != null) { // set header values webRequest.Method = "GET"; webRequest.ContentType = "application/json"; webRequest.UserAgent = "SCMKIT-5dc493ada400c79dd318abbe770dac7c"; // if we need to pass the token obtained from the username/password auth if (credential.Contains(":")) { webRequest.Headers.Add("Authorization", "Bearer " + accessToken); } // if user just specified personal access token else { webRequest.Headers.Add("PRIVATE-TOKEN", credential); } // get the response var httpResponse = (HttpWebResponse)webRequest.GetResponse(); var streamReader = new StreamReader(httpResponse.GetResponseStream()); string result = streamReader.ReadToEnd(); // get all instances of the id field for the id of the runners IEnumerable startingIndexesId = library.Utils.AllIndexesOf(result, "\"id\":"); IEnumerable endingIndexesId = library.Utils.AllIndexesOf(result, "\"description\":"); List listOfIds = new List(); for (int i = 0; i < startingIndexesId.Count(); i++) { string theId = ""; theId = result.Substring(startingIndexesId.ElementAt(i) + "\"id\":".Length, endingIndexesId.ElementAt(i) - startingIndexesId.ElementAt(i) - "\"id\"".Length); theId = theId.Replace("\"],\"", ""); theId = theId.Replace("[", ""); theId = theId.Replace("]", ""); theId = theId.Replace("\"", ""); theId = theId.Replace(",", ""); listOfIds.Add(theId); } // get all instances of the name field for the name of the runners IEnumerable startingIndexesName = library.Utils.AllIndexesOf(result, "\"name\":"); IEnumerable endingIndexesName = library.Utils.AllIndexesOf(result, "\"online\":"); List listofNames = new List(); for (int i = 0; i < startingIndexesId.Count(); i++) { string theName = ""; theName = result.Substring(startingIndexesName.ElementAt(i) + "\"name\":".Length, endingIndexesName.ElementAt(i) - startingIndexesName.ElementAt(i) - "\"name\"".Length); theName = theName.Replace("\"],\"", ""); theName = theName.Replace("[", ""); theName = theName.Replace("]", ""); theName = theName.Replace("\"", ""); theName = theName.Replace(",", ""); listofNames.Add(theName); } // go through and add the id and name to the runner list array for (int i = 0; i < listOfIds.Count(); i++) { nameAndIDs.Add(new aRunner(listOfIds[i], listofNames[i], "")); } // get details of each runner for (int i = 0; i < nameAndIDs.Count(); i++) { GetRunnerDetails(credential, url, nameAndIDs[i].id, accessToken, nameAndIDs[i].name); } // print the details of each runner for (int i = 0; i < theRunners.Count(); i++) { // as long as projects assigned is not blank, display if (!theRunners[i].project.Equals("")) { Console.WriteLine("{0,5} | {1,20} | {2,50}", theRunners[i].id, theRunners[i].name, theRunners[i].project); } } } } catch (Exception ex) { // if user is not permitted to list all runners, then just list runners owned by current user if (ex.Message.Contains("(403) Forbidden")) { await getOwnedRunners(credential, url, options, "gitlab"); } // otherwise display normal error message and return else { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Could not get listing of all GitLab runners. Exception: " + ex.ToString()); Console.WriteLine(""); } } } // get runners owned by current user (non-admin) public static async Task getOwnedRunners(string credential, string url, string options, string system) { try { ServicePointManager.ServerCertificateValidationCallback = delegate (object s, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { return true; }; ServicePointManager.Expect100Continue = true; ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; // proceed with request to list runners accessible for a given user string accessToken = ""; // if username/password auth being used, get the access token first. this is needed for subsequent API requests if (credential.Contains(":")) { accessToken = library.GitLabUtils.GetAccessToken(credential, url); } // perform request to get list of runners as we will need the ID of the runners to get more details var webRequest = (HttpWebRequest)System.Net.WebRequest.Create(url + "/api/v4/runners"); if (webRequest != null) { // set header values webRequest.Method = "GET"; webRequest.ContentType = "application/json"; webRequest.UserAgent = "SCMKIT-5dc493ada400c79dd318abbe770dac7c"; // if we need to pass the token obtained from the username/password auth if (credential.Contains(":")) { webRequest.Headers.Add("Authorization", "Bearer " + accessToken); } // if user just specified personal access token else { webRequest.Headers.Add("PRIVATE-TOKEN", credential); } // get the response var httpResponse = (HttpWebResponse)webRequest.GetResponse(); var streamReader = new StreamReader(httpResponse.GetResponseStream()); string result = streamReader.ReadToEnd(); // get all instances of the id field for the id of the runners IEnumerable startingIndexesId = library.Utils.AllIndexesOf(result, "\"id\":"); IEnumerable endingIndexesId = library.Utils.AllIndexesOf(result, "\"description\":"); List listOfIds = new List(); for (int i = 0; i < startingIndexesId.Count(); i++) { string theId = ""; theId = result.Substring(startingIndexesId.ElementAt(i) + "\"id\":".Length, endingIndexesId.ElementAt(i) - startingIndexesId.ElementAt(i) - "\"id\"".Length); theId = theId.Replace("\"],\"", ""); theId = theId.Replace("[", ""); theId = theId.Replace("]", ""); theId = theId.Replace("\"", ""); theId = theId.Replace(",", ""); listOfIds.Add(theId); } // get all instances of the name field for the name of the runners IEnumerable startingIndexesName = library.Utils.AllIndexesOf(result, "\"name\":"); IEnumerable endingIndexesName = library.Utils.AllIndexesOf(result, "\"online\":"); List listofNames = new List(); for (int i = 0; i < startingIndexesId.Count(); i++) { string theName = ""; theName = result.Substring(startingIndexesName.ElementAt(i) + "\"name\":".Length, endingIndexesName.ElementAt(i) - startingIndexesName.ElementAt(i) - "\"name\"".Length); theName = theName.Replace("\"],\"", ""); theName = theName.Replace("[", ""); theName = theName.Replace("]", ""); theName = theName.Replace("\"", ""); theName = theName.Replace(",", ""); listofNames.Add(theName); } // go through and add the id and name to the runner list array for (int i = 0; i < listOfIds.Count(); i++) { nameAndIDs.Add(new aRunner(listOfIds[i], listofNames[i], "")); } // get details of each runner for (int i = 0; i < nameAndIDs.Count(); i++) { GetRunnerDetails(credential, url, nameAndIDs[i].id, accessToken, nameAndIDs[i].name); } // print the details of each runner for (int i = 0; i < theRunners.Count(); i++) { // as long as projects assigned is not blank, display if (!theRunners[i].project.Equals("")) { Console.WriteLine("{0,5} | {1,20} | {2,50}", theRunners[i].id, theRunners[i].name, theRunners[i].project); } } } } catch (Exception ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Could not get listing of GitLab runners. Exception: " + ex.ToString()); Console.WriteLine(""); } } // get the details for a runner based on ID public static void GetRunnerDetails(string credential, string url, string id, string accessToken, string name) { try { ServicePointManager.ServerCertificateValidationCallback = delegate (object s, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { return true; }; ServicePointManager.Expect100Continue = true; ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; // perform request to get runner details by ID var webRequest = (HttpWebRequest)System.Net.WebRequest.Create(url + "/api/v4/runners/" + id); if (webRequest != null) { // set header values webRequest.Method = "GET"; webRequest.ContentType = "application/json"; webRequest.UserAgent = "SCMKIT-5dc493ada400c79dd318abbe770dac7c"; // if we need to pass the token obtained from the username/password auth if (credential.Contains(":")) { webRequest.Headers.Add("Authorization", "Bearer " + accessToken); } // if user just specified personal access token else { webRequest.Headers.Add("PRIVATE-TOKEN", credential); } // get the response var httpResponse = (HttpWebResponse)webRequest.GetResponse(); var streamReader = new StreamReader(httpResponse.GetResponseStream()); string result = streamReader.ReadToEnd(); // get the url to the repo string[] splitResults = result.Split(','); for (int i = 0; i < splitResults.Length; i++) { if (splitResults[i].Contains("http_url_to_repo")) { string theUrl = splitResults[i].Replace("\"http_url_to_repo\":\"", ""); theUrl = theUrl.Replace("\"", ""); theRunners.Add(new aRunner(id, name, theUrl)); } } } } catch (Exception ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Could not get listing of GitLab runners. Exception: " + ex.ToString()); Console.WriteLine(""); } } } } ================================================ FILE: SCMKit/modules/gitlab/SnippetList.cs ================================================ using System; using System.Collections.Generic; using System.Net; using System.Net.Security; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; using System.IO; using Newtonsoft.Json; namespace SCMKit.modules.gitlab { // custom class to handle the JSON output of snippet result public class snippetResult { public string id { get; set; } public string title { get; set; } public string description { get; set; } public string visibility { get; set; } public string updatedAt { get; set; } public string createdAt { get; set; } public string project_id { get; set; } public string web_url { get; set; } public string raw_url { get; set; } public string ssh_url_to_repo { get; set; } public string http_url_to_repo { get; set; } public Dictionary author { get; set; } public string fileName { get; set; } public Dictionary[] files { get; set; } public snippetResult() { } } class SnippetList { public static async Task execute(string credential, string url, string options, string system) { // Generate module header Console.WriteLine(library.Utils.GenerateHeader("listsnippet", credential, url, options, system)); try { ServicePointManager.ServerCertificateValidationCallback = delegate (object s, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { return true; }; ServicePointManager.Expect100Continue = true; ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; await library.Utils.HeartbeatRequest(url); string accessToken = ""; // if username/password auth being used, get the access token first. this is needed for subsequent API requests if (credential.Contains(":")) { accessToken = library.GitLabUtils.GetAccessToken(credential, url); } // create table header string tableHeader = string.Format("{0,20} | {1,70}", "Title", "Raw URL"); Console.WriteLine(tableHeader); Console.WriteLine(new String('-', tableHeader.Length)); // get snippets for user var webRequest = (HttpWebRequest)System.Net.WebRequest.Create(url + "/api/v4/snippets"); if (webRequest != null) { // set header values webRequest.Method = "GET"; webRequest.ContentType = "application/json"; webRequest.UserAgent = "SCMKIT-5dc493ada400c79dd318abbe770dac7c"; // if we need to pass the token obtained from the username/password auth if (credential.Contains(":")) { webRequest.Headers.Add("Authorization", "Bearer " + accessToken); } // if user just specified personal access token else { webRequest.Headers.Add("PRIVATE-TOKEN", credential); } // get web response WebResponse myWebResponse = await webRequest.GetResponseAsync(); string content; var reader = new StreamReader(myWebResponse.GetResponseStream()); content = reader.ReadToEnd(); // parse the JSON output and display results List snippetResults = JsonConvert.DeserializeObject>(content); foreach (snippetResult item in snippetResults) { Console.WriteLine("{0,20} | {1,70}", item.title, item.raw_url); } } } catch (Exception ex) { Console.WriteLine(""); Console.WriteLine("[-] ERROR: Could not retrieve listing of snippets for current user. Exception: " + ex.ToString()); Console.WriteLine(""); } } } } ================================================ FILE: SCMKit/packages.config ================================================  ================================================ FILE: SCMKit.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.31727.386 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SCMKit", "SCMKit\SCMKit.csproj", "{266C644A-69B1-426B-A47C-1CF32B211F80}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {266C644A-69B1-426B-A47C-1CF32B211F80}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {266C644A-69B1-426B-A47C-1CF32B211F80}.Debug|Any CPU.Build.0 = Debug|Any CPU {266C644A-69B1-426B-A47C-1CF32B211F80}.Release|Any CPU.ActiveCfg = Release|Any CPU {266C644A-69B1-426B-A47C-1CF32B211F80}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {F7FA0DD4-D75B-4F67-91D1-A7019B776721} EndGlobalSection EndGlobal