├── .dockerignore ├── .gitattributes ├── .github └── workflows │ ├── build_preview.yml │ └── build_release.yml ├── .gitignore ├── Dockerfile ├── Docs └── schematic.png ├── Enums ├── Authors.cs ├── Directory.cs ├── Network.cs └── ShareType.cs ├── FileDiscovery ├── ACL.cs ├── CrossPlatformHelper.cs ├── Directory.cs ├── File.cs ├── FileFinder.cs ├── Output │ └── FileOutput.cs └── WindowsHelper.cs ├── HostDiscovery ├── Host.cs └── HostFinder.cs ├── Kibana ├── README.md ├── docker-compose.yml └── export.ndjson ├── LICENSE ├── NetworkDiscovery ├── Network.cs └── NetworkFinder.cs ├── Output ├── IOutputPayload.cs └── OutputHelper.cs ├── Program.cs ├── README.md ├── SMBeagle.csproj ├── SMBeagle.sln ├── ShareDiscovery ├── CrossPlatformShareFinder.cs ├── Share.cs └── WindowsShareFinder.cs └── tests ├── Dockerfile ├── Dockerfile.Linux ├── Dockerfile.Windows ├── tests ├── helpers.py ├── test_010_options.py ├── test_020_tcp_scans.py ├── test_030_output.py └── test_040_smb_scans.py └── windows_scripts └── install_python.ps1 /.dockerignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .github 3 | .git 4 | .gitignore 5 | .gitattributes 6 | bin 7 | obj 8 | Docs 9 | Dockerfile 10 | LICENSE 11 | README.md 12 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.github/workflows/build_preview.yml: -------------------------------------------------------------------------------- 1 | name: Build and upload preview build 2 | 3 | on: 4 | pull_request: 5 | branches: [ main ] 6 | types: 7 | - synchronize 8 | - opened 9 | - reopened 10 | 11 | jobs: 12 | build_linux: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v4 17 | - name: Setup .NET 18 | uses: actions/setup-dotnet@v1 19 | with: 20 | dotnet-version: 9.0.x 21 | - name: Restore dependencies 22 | run: dotnet restore 23 | - name: Build linux x64 24 | run: dotnet publish -c Release --self-contained -r linux-x64 -o packages/linux/amd64 -p:PublishSingleFile=true -p:PublishTrimmed=false -p:InvariantGlobalization=true -p:DebugType=None -p:DebugSymbols=false -p:VersionSuffix=pr$(echo $GITHUB_REF | awk 'BEGIN { FS = "/" } ; { print $3 }') 25 | - name: Build linux arm 26 | run: dotnet publish -c Release --self-contained -r linux-arm64 -o packages/linux/arm64 -p:PublishSingleFile=true -p:PublishTrimmed=false -p:InvariantGlobalization=true -p:DebugType=None -p:DebugSymbols=false -p:DebugSymbols=false -p:VersionSuffix=pr$(echo $GITHUB_REF | awk 'BEGIN { FS = "/" } ; { print $3 }') 27 | - name: Set up QEMU 28 | uses: docker/setup-qemu-action@v1 29 | - name: Set up Docker Buildx 30 | id: buildx 31 | uses: docker/setup-buildx-action@v1 32 | - name: Run Buildx 33 | run: | 34 | docker buildx build . \ 35 | --progress=plain \ 36 | --platform linux/amd64,linux/arm64 37 | - name: Upload linux x64 38 | uses: actions/upload-artifact@v4 39 | with: 40 | name: linux-x64 41 | path: packages/linux/amd64/* 42 | - name: Upload linux ARM 43 | uses: actions/upload-artifact@v4 44 | with: 45 | name: linux-arm 46 | path: packages/linux/arm64/* 47 | - name: install test dependencies 48 | run: | 49 | python -m venv /tmp/venv 50 | source /tmp/venv/bin/activate 51 | pip install impacket pytest 52 | - name: copy beagle into path 53 | run: | 54 | sudo cp packages/linux/amd64/SMBeagle /bin/smbeagle 55 | sudo chmod +x /bin/smbeagle 56 | sudo mkdir /empty_dir 57 | - name: run pytest 58 | run: | 59 | cd tests 60 | sudo ROOTDIR=/ /tmp/venv/bin/pytest -v -k "not on_windows" 61 | build_windows: 62 | runs-on: windows-2019 63 | steps: 64 | - uses: actions/checkout@v4 65 | - name: Setup .NET 66 | uses: actions/setup-dotnet@v1 67 | with: 68 | dotnet-version: 9.0.x 69 | - name: Restore dependencies 70 | run: dotnet restore 71 | - name: Build windows x64 72 | shell: bash 73 | run: dotnet publish -c Release --self-contained -r win-x64 -o packages/windows/x64 -p:PublishSingleFile=true -p:PublishTrimmed=false -p:InvariantGlobalization=true -p:DebugType=None -p:DebugSymbols=false -p:VersionSuffix=pr$(echo $GITHUB_REF | awk 'BEGIN { FS = "/" } ; { print $3 }') 74 | # - name: Install signing tool 75 | # run: dotnet tool install --global AzureSignTool --version 3.0.0 76 | # - name: Digitally sign executable 77 | # env: 78 | # CERT_APP_SECRET: ${{ secrets.AZUREKEYVAULTCLIENTSECRET }} 79 | # CERT_APP_ID: ${{ secrets.AZUREKEYVAULTAPPLICATIONID }} 80 | # TENANT_ID: ${{ secrets.AZUREKEYVAULTTENANTID }} 81 | # VAULT_URI: ${{ secrets.AZUREKEYVAULTURI }} 82 | # run: azuresigntool sign -kvt "$env:TENANT_ID" -kvu "$env:VAULT_URI" -kvi "$env:CERT_APP_ID" -kvs "$env:CERT_APP_SECRET" -kvc "PunkSecurity-CodeSigningCert-2022" -tr http://timestamp.globalsign.com/tsa/r6advanced1 -v .\packages\windows\x64\SMBeagle.exe 83 | - name: Upload windows x64 84 | uses: actions/upload-artifact@v4 85 | with: 86 | name: windows-x64 87 | path: packages\windows/x64\* 88 | -------------------------------------------------------------------------------- /.github/workflows/build_release.yml: -------------------------------------------------------------------------------- 1 | name: Build and publish release on new tag 2 | on: 3 | push: 4 | tags: 5 | - '*' 6 | 7 | jobs: 8 | create-release: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: version 12 | run: echo ::set-output name=version::$(echo $GITHUB_REF | cut -d / -f 3) 13 | id: version 14 | - name: Create new release 15 | uses: actions/create-release@v1 16 | id: create_release 17 | with: 18 | draft: false 19 | prerelease: false 20 | release_name: ${{ steps.version.outputs.version }} 21 | tag_name: ${{ github.ref }} 22 | env: 23 | GITHUB_TOKEN: ${{ github.token }} 24 | outputs: 25 | release_upload_url: ${{ steps.create_release.outputs.upload_url }} 26 | release_version: ${{ steps.version.outputs.version }} 27 | 28 | build-linux: 29 | runs-on: ubuntu-latest 30 | needs: create-release 31 | steps: 32 | - name: Checkout 33 | uses: actions/checkout@v4 34 | - name: Setup .NET 35 | uses: actions/setup-dotnet@v1 36 | with: 37 | dotnet-version: 9.0.x 38 | - name: Restore dependencies 39 | run: dotnet restore 40 | - name: Build linux x64 41 | run: dotnet publish -c Release --self-contained -r linux-x64 -o packages/linux/amd64 -p:PublishSingleFile=true -p:PublishTrimmed=false -p:InvariantGlobalization=true -p:DebugType=None -p:DebugSymbols=false -p:Version=${{ needs.create-release.outputs.release_version }} 42 | - name: Build linux arm 43 | run: dotnet publish -c Release --self-contained -r linux-arm64 -o packages/linux/arm64 -p:PublishSingleFile=true -p:PublishTrimmed=false -p:InvariantGlobalization=true -p:DebugType=None -p:DebugSymbols=false -p:Version=${{ needs.create-release.outputs.release_version }} 44 | - name: Login to DockerHub 45 | uses: docker/login-action@v1 46 | with: 47 | # TODO: replace username with action variable 48 | username: punksecurity 49 | password: ${{ secrets.DOCKER_ACCESS_TOKEN }} 50 | - name: Set up Docker Buildx 51 | id: buildx 52 | uses: docker/setup-buildx-action@v1 53 | - name: Run Buildx 54 | run: | 55 | docker buildx build . \ 56 | --push \ 57 | --progress=plain \ 58 | --tag docker.io/punksecurity/smbeagle:${{ needs.create-release.outputs.release_version }} \ 59 | --tag docker.io/punksecurity/smbeagle:latest \ 60 | --platform linux/amd64,linux/arm64 61 | - name: Create Linux x64 archive 62 | run: cd packages/linux/amd64 && zip linux_amd64.zip SMBeagle 63 | - name: Create Linux arm archive 64 | run: cd packages/linux/arm64 && zip linux_arm64.zip SMBeagle 65 | - name: Upload Linux x64 binary 66 | uses: actions/upload-release-asset@v1 67 | env: 68 | GITHUB_TOKEN: ${{ github.token }} 69 | with: 70 | upload_url: ${{ needs.create-release.outputs.release_upload_url }} 71 | asset_path: ./packages/linux/amd64/linux_amd64.zip 72 | asset_name: smbeagle_${{ needs.create-release.outputs.release_version }}_linux_amd64.zip 73 | asset_content_type: application/zip 74 | - name: Upload Linux ARM binary 75 | uses: actions/upload-release-asset@v1 76 | env: 77 | GITHUB_TOKEN: ${{ github.token }} 78 | with: 79 | upload_url: ${{ needs.create-release.outputs.release_upload_url }} 80 | asset_path: ./packages/linux/arm64/linux_arm64.zip 81 | asset_name: smbeagle_${{ needs.create-release.outputs.release_version }}_linux_arm64.zip 82 | asset_content_type: application/zip 83 | 84 | build-windows: 85 | runs-on: windows-2019 86 | needs: create-release 87 | steps: 88 | - uses: actions/checkout@v4 89 | - name: Setup .NET 90 | uses: actions/setup-dotnet@v1 91 | with: 92 | dotnet-version: 9.0.x 93 | - name: Restore dependencies 94 | run: dotnet restore 95 | - name: Build windows x64 96 | run: dotnet publish -c Release --self-contained -r win-x64 -o packages\windows\x64 -p:PublishSingleFile=true -p:PublishTrimmed=false -p:InvariantGlobalization=true -p:DebugType=None -p:DebugSymbols=false -p:Version=${{ needs.create-release.outputs.release_version }} 97 | # - name: Install signing tool 98 | # run: dotnet tool install --global AzureSignTool --version 3.0.0 99 | # - name: Digitally sign executable 100 | # env: 101 | # CERT_APP_SECRET: ${{ secrets.AZUREKEYVAULTCLIENTSECRET }} 102 | # CERT_APP_ID: ${{ secrets.AZUREKEYVAULTAPPLICATIONID }} 103 | # TENANT_ID: ${{ secrets.AZUREKEYVAULTTENANTID }} 104 | # VAULT_URI: ${{ secrets.AZUREKEYVAULTURI }} 105 | # run: azuresigntool sign -kvt "$env:TENANT_ID" -kvu "$env:VAULT_URI" -kvi "$env:CERT_APP_ID" -kvs "$env:CERT_APP_SECRET" -kvc "PunkSecurity-CodeSigningCert-2022" -tr http://timestamp.globalsign.com/tsa/r6advanced1 -v .\packages\windows\x64\SMBeagle.exe 106 | 107 | - name: Create Windows archive 108 | run: Compress-Archive -DestinationPath .\win_x64.zip -Path .\packages\windows\x64 109 | - name: Upload Win x64 binary 110 | uses: actions/upload-release-asset@v1 111 | env: 112 | GITHUB_TOKEN: ${{ github.token }} 113 | with: 114 | upload_url: ${{ needs.create-release.outputs.release_upload_url }} 115 | asset_path: .\win_x64.zip 116 | asset_name: smbeagle_${{ needs.create-release.outputs.release_version }}_win_x64.zip 117 | asset_content_type: application/zip 118 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | .idea 13 | 14 | # User-specific files (MonoDevelop/Xamarin Studio) 15 | *.userprefs 16 | 17 | # Build results 18 | [Dd]ebug/ 19 | [Dd]ebugPublic/ 20 | [Rr]elease/ 21 | [Rr]eleases/ 22 | x64/ 23 | x86/ 24 | [Aa][Rr][Mm]/ 25 | [Aa][Rr][Mm]64/ 26 | bld/ 27 | [Bb]in/ 28 | [Oo]bj/ 29 | [Ll]og/ 30 | 31 | # Visual Studio 2015/2017 cache/options directory 32 | .vs/ 33 | # Uncomment if you have tasks that create the project's static files in wwwroot 34 | #wwwroot/ 35 | 36 | # Visual Studio 2017 auto generated files 37 | Generated\ Files/ 38 | 39 | # MSTest test Results 40 | [Tt]est[Rr]esult*/ 41 | [Bb]uild[Ll]og.* 42 | 43 | # NUNIT 44 | *.VisualState.xml 45 | TestResult.xml 46 | 47 | # Build Results of an ATL Project 48 | [Dd]ebugPS/ 49 | [Rr]eleasePS/ 50 | dlldata.c 51 | 52 | # Benchmark Results 53 | BenchmarkDotNet.Artifacts/ 54 | 55 | # .NET Core 56 | project.lock.json 57 | project.fragment.lock.json 58 | artifacts/ 59 | 60 | # StyleCop 61 | StyleCopReport.xml 62 | 63 | # Files built by Visual Studio 64 | *_i.c 65 | *_p.c 66 | *_h.h 67 | *.ilk 68 | *.meta 69 | *.obj 70 | *.iobj 71 | *.pch 72 | *.pdb 73 | *.ipdb 74 | *.pgc 75 | *.pgd 76 | *.rsp 77 | *.sbr 78 | *.tlb 79 | *.tli 80 | *.tlh 81 | *.tmp 82 | *.tmp_proj 83 | *_wpftmp.csproj 84 | *.log 85 | *.vspscc 86 | *.vssscc 87 | .builds 88 | *.pidb 89 | *.svclog 90 | *.scc 91 | 92 | # Chutzpah Test files 93 | _Chutzpah* 94 | 95 | # Visual C++ cache files 96 | ipch/ 97 | *.aps 98 | *.ncb 99 | *.opendb 100 | *.opensdf 101 | *.sdf 102 | *.cachefile 103 | *.VC.db 104 | *.VC.VC.opendb 105 | 106 | # Visual Studio profiler 107 | *.psess 108 | *.vsp 109 | *.vspx 110 | *.sap 111 | 112 | # Visual Studio Trace Files 113 | *.e2e 114 | 115 | # TFS 2012 Local Workspace 116 | $tf/ 117 | 118 | # Guidance Automation Toolkit 119 | *.gpState 120 | 121 | # ReSharper is a .NET coding add-in 122 | _ReSharper*/ 123 | *.[Rr]e[Ss]harper 124 | *.DotSettings.user 125 | 126 | # JustCode is a .NET coding add-in 127 | .JustCode 128 | 129 | # TeamCity is a build add-in 130 | _TeamCity* 131 | 132 | # DotCover is a Code Coverage Tool 133 | *.dotCover 134 | 135 | # AxoCover is a Code Coverage Tool 136 | .axoCover/* 137 | !.axoCover/settings.json 138 | 139 | # Visual Studio code coverage results 140 | *.coverage 141 | *.coveragexml 142 | 143 | # NCrunch 144 | _NCrunch_* 145 | .*crunch*.local.xml 146 | nCrunchTemp_* 147 | 148 | # MightyMoose 149 | *.mm.* 150 | AutoTest.Net/ 151 | 152 | # Web workbench (sass) 153 | .sass-cache/ 154 | 155 | # Installshield output folder 156 | [Ee]xpress/ 157 | 158 | # DocProject is a documentation generator add-in 159 | DocProject/buildhelp/ 160 | DocProject/Help/*.HxT 161 | DocProject/Help/*.HxC 162 | DocProject/Help/*.hhc 163 | DocProject/Help/*.hhk 164 | DocProject/Help/*.hhp 165 | DocProject/Help/Html2 166 | DocProject/Help/html 167 | 168 | # Click-Once directory 169 | publish/ 170 | 171 | # Publish Web Output 172 | *.[Pp]ublish.xml 173 | *.azurePubxml 174 | # Note: Comment the next line if you want to checkin your web deploy settings, 175 | # but database connection strings (with potential passwords) will be unencrypted 176 | *.pubxml 177 | *.publishproj 178 | 179 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 180 | # checkin your Azure Web App publish settings, but sensitive information contained 181 | # in these scripts will be unencrypted 182 | PublishScripts/ 183 | 184 | # NuGet Packages 185 | *.nupkg 186 | # The packages folder can be ignored because of Package Restore 187 | **/[Pp]ackages/* 188 | # except build/, which is used as an MSBuild target. 189 | !**/[Pp]ackages/build/ 190 | # Uncomment if necessary however generally it will be regenerated when needed 191 | #!**/[Pp]ackages/repositories.config 192 | # NuGet v3's project.json files produces more ignorable files 193 | *.nuget.props 194 | *.nuget.targets 195 | 196 | # Microsoft Azure Build Output 197 | csx/ 198 | *.build.csdef 199 | 200 | # Microsoft Azure Emulator 201 | ecf/ 202 | rcf/ 203 | 204 | # Windows Store app package directories and files 205 | AppPackages/ 206 | BundleArtifacts/ 207 | Package.StoreAssociation.xml 208 | _pkginfo.txt 209 | *.appx 210 | 211 | # Visual Studio cache files 212 | # files ending in .cache can be ignored 213 | *.[Cc]ache 214 | # but keep track of directories ending in .cache 215 | !?*.[Cc]ache/ 216 | 217 | # Others 218 | ClientBin/ 219 | ~$* 220 | *~ 221 | *.dbmdl 222 | *.dbproj.schemaview 223 | *.jfm 224 | *.pfx 225 | *.publishsettings 226 | orleans.codegen.cs 227 | 228 | # Including strong name files can present a security risk 229 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 230 | #*.snk 231 | 232 | # Since there are multiple workflows, uncomment next line to ignore bower_components 233 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 234 | #bower_components/ 235 | 236 | # RIA/Silverlight projects 237 | Generated_Code/ 238 | 239 | # Backup & report files from converting an old project file 240 | # to a newer Visual Studio version. Backup files are not needed, 241 | # because we have git ;-) 242 | _UpgradeReport_Files/ 243 | Backup*/ 244 | UpgradeLog*.XML 245 | UpgradeLog*.htm 246 | ServiceFabricBackup/ 247 | *.rptproj.bak 248 | 249 | # SQL Server files 250 | *.mdf 251 | *.ldf 252 | *.ndf 253 | 254 | # Business Intelligence projects 255 | *.rdl.data 256 | *.bim.layout 257 | *.bim_*.settings 258 | *.rptproj.rsuser 259 | *- Backup*.rdl 260 | 261 | # Microsoft Fakes 262 | FakesAssemblies/ 263 | 264 | # GhostDoc plugin setting file 265 | *.GhostDoc.xml 266 | 267 | # Node.js Tools for Visual Studio 268 | .ntvs_analysis.dat 269 | node_modules/ 270 | 271 | # Visual Studio 6 build log 272 | *.plg 273 | 274 | # Visual Studio 6 workspace options file 275 | *.opt 276 | 277 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 278 | *.vbw 279 | 280 | # Visual Studio LightSwitch build output 281 | **/*.HTMLClient/GeneratedArtifacts 282 | **/*.DesktopClient/GeneratedArtifacts 283 | **/*.DesktopClient/ModelManifest.xml 284 | **/*.Server/GeneratedArtifacts 285 | **/*.Server/ModelManifest.xml 286 | _Pvt_Extensions 287 | 288 | # Paket dependency manager 289 | .paket/paket.exe 290 | paket-files/ 291 | 292 | # FAKE - F# Make 293 | .fake/ 294 | 295 | # JetBrains Rider 296 | .idea/ 297 | *.sln.iml 298 | 299 | # CodeRush personal settings 300 | .cr/personal 301 | 302 | # Python Tools for Visual Studio (PTVS) 303 | __pycache__/ 304 | *.pyc 305 | 306 | # Cake - Uncomment if you are using it 307 | # tools/** 308 | # !tools/packages.config 309 | 310 | # Tabs Studio 311 | *.tss 312 | 313 | # Telerik's JustMock configuration file 314 | *.jmconfig 315 | 316 | # BizTalk build output 317 | *.btp.cs 318 | *.btm.cs 319 | *.odx.cs 320 | *.xsd.cs 321 | 322 | # OpenCover UI analysis results 323 | OpenCover/ 324 | 325 | # Azure Stream Analytics local run output 326 | ASALocalRun/ 327 | 328 | # MSBuild Binary and Structured Log 329 | *.binlog 330 | 331 | # NVidia Nsight GPU debugger configuration file 332 | *.nvuser 333 | 334 | # MFractors (Xamarin productivity tool) working folder 335 | .mfractor/ 336 | 337 | # Local History for Visual Studio 338 | .localhistory/ 339 | 340 | # BeatPulse healthcheck temp database 341 | healthchecksdb 342 | 343 | Properties 344 | Properties/launchSettings.json 345 | BuildConfig.json -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian 2 | ARG TARGETARCH 3 | COPY packages/linux/${TARGETARCH}/SMBeagle /bin/smbeagle 4 | 5 | RUN chmod +x /bin/smbeagle 6 | 7 | ENTRYPOINT ["smbeagle", "-D"] 8 | -------------------------------------------------------------------------------- /Docs/schematic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/punk-security/smbeagle/2926ca70ff193b0c37de345576a5b79e7fab3487/Docs/schematic.png -------------------------------------------------------------------------------- /Enums/Authors.cs: -------------------------------------------------------------------------------- 1 | namespace SMBeagle.Enums 2 | { 3 | public enum OutputtersEnum 4 | { 5 | File 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Enums/Directory.cs: -------------------------------------------------------------------------------- 1 | namespace SMBeagle.Enums 2 | { 3 | public enum DirectoryTypeEnum 4 | { 5 | UNKNOWN = 0, 6 | SMB = 1, 7 | LOCAL_REMOVEABLE = 2, 8 | LOCAL_FIXED = 4, 9 | LOCAL_NETWORK = 8, 10 | LOCAL_CDROM = 16, 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Enums/Network.cs: -------------------------------------------------------------------------------- 1 | namespace SMBeagle.Enums 2 | { 3 | public enum NetworkDiscoverySourceEnum 4 | { 5 | NETSTAT, 6 | LOCAL, 7 | ARGS 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Enums/ShareType.cs: -------------------------------------------------------------------------------- 1 | namespace SMBeagle.Enums 2 | { 3 | public enum ShareTypeEnum 4 | { 5 | DISK, 6 | CLUSTER_SHARE, 7 | SCALE_OUT_CLUSTER_SHARE, 8 | DFS_SHARE, 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /FileDiscovery/ACL.cs: -------------------------------------------------------------------------------- 1 | namespace SMBeagle.FileDiscovery 2 | { 3 | class ACL 4 | { 5 | public bool Readable; 6 | public bool Writeable; 7 | public bool Deletable; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /FileDiscovery/CrossPlatformHelper.cs: -------------------------------------------------------------------------------- 1 | using SMBLibrary.Client; 2 | using SMBLibrary; 3 | using System; 4 | using System.IO; 5 | 6 | namespace SMBeagle.FileDiscovery 7 | { 8 | class CrossPlatformHelper 9 | { 10 | private static bool CheckAccessMask(ISMBFileStore fileStore, string filePath, AccessMask accessMask) 11 | { 12 | if (fileStore is SMB1FileStore) 13 | { 14 | filePath = @"\\" + filePath; 15 | } 16 | object handle; 17 | NTStatus result = fileStore.CreateFile(out handle, out _, filePath, accessMask, SMBLibrary.FileAttributes.Normal, ShareAccess.None, CreateDisposition.FILE_OPEN, CreateOptions.FILE_NON_DIRECTORY_FILE | CreateOptions.FILE_SYNCHRONOUS_IO_ALERT, null); 18 | if (result == NTStatus.STATUS_SUCCESS) 19 | fileStore.CloseFile(handle); 20 | return result == NTStatus.STATUS_SUCCESS; 21 | } 22 | public static ACL ResolvePermissions(File file) 23 | { 24 | ACL acl = new(); 25 | if (file.ParentDirectory.Share == null) 26 | { 27 | Console.WriteLine("ERROR: File does not have a parent share"); 28 | Environment.Exit(1); 29 | } 30 | NTStatus status; 31 | //TODO: optimise storing filestore somewhere 32 | ISMBFileStore fileStore = file.ParentDirectory.Share.Host.Client.TreeConnect(file.ParentDirectory.Share.Name, out status); 33 | 34 | if (status != NTStatus.STATUS_SUCCESS) 35 | { 36 | Console.WriteLine("ERROR: Could not connect to share"); 37 | } 38 | string filePath = file.FullName; 39 | acl.Readable = CheckAccessMask(fileStore, filePath, AccessMask.GENERIC_READ | AccessMask.SYNCHRONIZE); 40 | acl.Writeable = CheckAccessMask(fileStore, filePath, AccessMask.GENERIC_WRITE | AccessMask.SYNCHRONIZE); 41 | acl.Deletable = CheckAccessMask(fileStore, filePath, AccessMask.DELETE | AccessMask.SYNCHRONIZE); 42 | fileStore.Disconnect(); 43 | return acl; 44 | } 45 | public static void RetrieveFile(File file, string outputPath) 46 | { 47 | //TODO: Check file size and gate 48 | NTStatus status; 49 | ISMBFileStore fileStore = file.ParentDirectory.Share.Host.Client.TreeConnect(file.ParentDirectory.Share.Name, out status); 50 | if (status != NTStatus.STATUS_SUCCESS) 51 | { 52 | Console.WriteLine("ERROR: Could not connect to share"); 53 | } 54 | object handle; 55 | byte[] filebytes; 56 | NTStatus result = fileStore.CreateFile(out handle, out _, file.FullName, AccessMask.GENERIC_READ | AccessMask.SYNCHRONIZE, SMBLibrary.FileAttributes.Normal, ShareAccess.None, CreateDisposition.FILE_OPEN, CreateOptions.FILE_NON_DIRECTORY_FILE | CreateOptions.FILE_SYNCHRONOUS_IO_ALERT, null); 57 | if (result == NTStatus.STATUS_SUCCESS) 58 | { 59 | // Open a FileStream to write chunks to the local file 60 | using (FileStream localFileStream = new FileStream(outputPath, FileMode.Create, FileAccess.Write)) 61 | { 62 | long bytesRead = 0; 63 | byte[] data; 64 | 65 | while (true) 66 | { 67 | // Read the next chunk from the remote file 68 | status = fileStore.ReadFile(out data, handle, bytesRead, (int)file.ParentDirectory.Share.Host.Client.MaxReadSize); 69 | 70 | // Check for read errors or end of file 71 | if (status != NTStatus.STATUS_SUCCESS && status != NTStatus.STATUS_END_OF_FILE) 72 | { 73 | throw new Exception("Failed to read from file. Status: " + status.ToString()); 74 | } 75 | 76 | if (status == NTStatus.STATUS_END_OF_FILE || data.Length == 0) 77 | { 78 | // Done reading 79 | break; 80 | } 81 | 82 | // Write the chunk to the local file 83 | localFileStream.Write(data, 0, data.Length); 84 | 85 | // Update total bytes read 86 | bytesRead += data.Length; 87 | } 88 | } 89 | 90 | // Close the remote file 91 | status = fileStore.CloseFile(handle); 92 | } 93 | // Disconnect from the SMB file store 94 | status = fileStore.Disconnect(); 95 | } 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /FileDiscovery/Directory.cs: -------------------------------------------------------------------------------- 1 | using SMBeagle.ShareDiscovery; 2 | using SMBLibrary; 3 | using SMBLibrary.Client; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.IO; 7 | 8 | namespace SMBeagle.FileDiscovery 9 | { 10 | class Directory 11 | { 12 | public Share Share { get; set; } 13 | public string Path { get; set; } 14 | public string UNCPath 15 | { 16 | get 17 | { 18 | // Windows enum needs UNC Paths as Path but Cross-platform doesnt. 19 | if (Path.StartsWith(@"\\")) 20 | return Path; 21 | else 22 | return $"{Share.uncPath}{Path}"; 23 | } 24 | } 25 | //todo: replace Base and Type with direct copy from parent then drop the ref 26 | #nullable enable 27 | public Directory? Parent { get; set; } = null; 28 | #nullable disable 29 | public Directory Base { get 30 | { 31 | if (Parent == null) 32 | return this; 33 | else 34 | return Parent.Base; 35 | } 36 | } 37 | public Enums.DirectoryTypeEnum DirectoryType { get; set; } = Enums.DirectoryTypeEnum.UNKNOWN; 38 | public List RecursiveFiles 39 | { 40 | get 41 | { 42 | List ret = new List(); 43 | ret.AddRange(Files); 44 | foreach (Directory dir in ChildDirectories) 45 | { 46 | ret.AddRange(dir.RecursiveFiles); 47 | } 48 | return ret; 49 | } 50 | } 51 | 52 | public List RecursiveChildDirectories 53 | { 54 | get 55 | { 56 | List ret = new List(); 57 | ret.AddRange(ChildDirectories); 58 | foreach (Directory dir in ChildDirectories) 59 | { 60 | ret.AddRange(dir.ChildDirectories); 61 | } 62 | return ret; 63 | } 64 | } 65 | 66 | public List Files { get; private set; } = new List(); 67 | public List ChildDirectories { get; private set; } = new List(); 68 | public Directory(string path, Share share) 69 | { 70 | Share = share; 71 | Path = path; 72 | } 73 | public void FindFilesWindows(List extensionsToIgnore = null) 74 | { 75 | try 76 | { 77 | FileInfo[] files = new DirectoryInfo(UNCPath).GetFiles("*.*"); 78 | foreach (FileInfo file in files) 79 | { 80 | if (extensionsToIgnore.Contains(file.Extension.ToLower())) 81 | continue; 82 | Files.Add( 83 | new File( 84 | parentDirectory: this, 85 | name: file.Name, 86 | fullName: file.FullName, 87 | extension: file.Extension, 88 | creationTime: file.CreationTime, 89 | lastWriteTime: file.LastWriteTime 90 | ) 91 | ); 92 | } 93 | } 94 | catch { } 95 | } 96 | public void FindFilesCrossPlatform(List extensionsToIgnore = null) 97 | { 98 | try 99 | { 100 | NTStatus status; 101 | ISMBFileStore fileStore = Share.Host.Client.TreeConnect(Share.Name, out status); 102 | if (status == NTStatus.STATUS_SUCCESS) 103 | { 104 | object directoryHandle; 105 | FileStatus fileStatus; 106 | status = fileStore.CreateFile(out directoryHandle, out fileStatus, Path, AccessMask.GENERIC_READ, SMBLibrary.FileAttributes.Directory, ShareAccess.Read | ShareAccess.Write, CreateDisposition.FILE_OPEN, CreateOptions.FILE_DIRECTORY_FILE, null); 107 | if (status == NTStatus.STATUS_SUCCESS) 108 | { 109 | List fileList; 110 | //TODO: can we filter on just files 111 | fileStore.QueryDirectory(out fileList, directoryHandle, "*", FileInformationClass.FileDirectoryInformation); 112 | foreach (QueryDirectoryFileInformation f in fileList) 113 | { 114 | if (f.FileInformationClass == FileInformationClass.FileDirectoryInformation) 115 | { 116 | FileDirectoryInformation d = (FileDirectoryInformation)f; 117 | if (! d.FileAttributes.HasFlag(SMBLibrary.FileAttributes.Directory)) 118 | { 119 | string extension = d.FileName.Substring(d.FileName.LastIndexOf('.') + 1); 120 | string path; 121 | if (Path == "") 122 | path = d.FileName; 123 | else 124 | path = $"{Path}\\{d.FileName}"; 125 | if (extensionsToIgnore.Contains(extension.ToLower())) 126 | continue; 127 | Files.Add( 128 | new File( 129 | parentDirectory: this, 130 | name: d.FileName, 131 | fullName: path, 132 | extension: extension, 133 | creationTime: d.CreationTime, 134 | lastWriteTime: d.LastWriteTime 135 | ) 136 | ); 137 | } 138 | } 139 | } 140 | fileStore.CloseFile(directoryHandle); 141 | } 142 | fileStore.Disconnect(); 143 | } 144 | } 145 | catch 146 | { 147 | //TODO: Implement better error handling here, one explosion should not wipe out the whole enumeration 148 | } 149 | } 150 | public void Clear() 151 | { 152 | Files.Clear(); 153 | ChildDirectories.Clear(); 154 | } 155 | 156 | private void FindDirectoriesWindows() 157 | { 158 | try 159 | { 160 | DirectoryInfo[] subDirs = new DirectoryInfo(UNCPath).GetDirectories(); 161 | foreach (DirectoryInfo di in subDirs) 162 | ChildDirectories.Add(new Directory(path: di.FullName, share: Share) { Parent = this}); 163 | } 164 | catch { } 165 | } 166 | private void FindDirectoriesCrossPlatform() 167 | { 168 | try 169 | { 170 | NTStatus status; 171 | ISMBFileStore fileStore = Share.Host.Client.TreeConnect(Share.Name, out status); 172 | if (status == NTStatus.STATUS_SUCCESS) 173 | { 174 | object directoryHandle; 175 | FileStatus fileStatus; 176 | status = fileStore.CreateFile(out directoryHandle, out fileStatus, Path, AccessMask.GENERIC_READ, SMBLibrary.FileAttributes.Directory, ShareAccess.Read | ShareAccess.Write, CreateDisposition.FILE_OPEN, CreateOptions.FILE_DIRECTORY_FILE, null); 177 | if (status == NTStatus.STATUS_SUCCESS) 178 | { 179 | List fileList; 180 | //TODO: can we filter on just files 181 | fileStore.QueryDirectory(out fileList, directoryHandle, "*", FileInformationClass.FileDirectoryInformation); 182 | foreach (QueryDirectoryFileInformation f in fileList) 183 | { 184 | if (f.FileInformationClass == FileInformationClass.FileDirectoryInformation) 185 | { 186 | FileDirectoryInformation d = (FileDirectoryInformation) f; 187 | if (d.FileAttributes.HasFlag(SMBLibrary.FileAttributes.Directory) && d.FileName != "." && d.FileName != "..") 188 | { 189 | string path = ""; 190 | if (Path != "") 191 | path += $"{Path}\\"; 192 | path += d.FileName; 193 | ChildDirectories.Add(new Directory(path: path, share: Share) { Parent = this }); 194 | } 195 | } 196 | } 197 | fileStore.CloseFile(directoryHandle); 198 | } 199 | fileStore.Disconnect(); 200 | } 201 | } 202 | catch 203 | { 204 | //TODO: Implement better error handling here, one explosion should not wipe out the whole enumeration 205 | } 206 | } 207 | public void FindDirectoriesRecursively(bool crossPlatform, ref bool abort) 208 | { 209 | if (crossPlatform) 210 | FindDirectoriesCrossPlatform(); 211 | else 212 | FindDirectoriesWindows(); 213 | foreach (Directory dir in ChildDirectories) 214 | { 215 | if (abort) 216 | return; 217 | dir.FindDirectoriesRecursively(crossPlatform, ref abort); 218 | } 219 | } 220 | 221 | public void FindFilesRecursively(bool crossPlatform, ref bool abort, List extensionsToIgnore = null) 222 | { 223 | if (crossPlatform) 224 | FindFilesCrossPlatform(extensionsToIgnore); 225 | else 226 | FindFilesWindows(extensionsToIgnore); 227 | foreach (Directory dir in RecursiveChildDirectories) 228 | { 229 | if (abort) 230 | return; 231 | dir.FindFilesRecursively(crossPlatform, ref abort, extensionsToIgnore); 232 | } 233 | } 234 | 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /FileDiscovery/File.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SMBeagle.FileDiscovery 4 | { 5 | class File 6 | { 7 | public Directory ParentDirectory { get; set; } 8 | public string FullName { get; set; } 9 | public string Name { get; set; } 10 | public string Extension { get; set; } 11 | public bool Readable { get; set; } 12 | public bool Writeable { get; set; } 13 | public bool Deletable { get; set; } 14 | public DateTime CreationTime { get; set; } 15 | public DateTime LastWriteTime { get; set; } 16 | 17 | public File(string name, string fullName, string extension, DateTime creationTime, DateTime lastWriteTime, Directory parentDirectory) 18 | { 19 | Name = name; 20 | Extension = extension; 21 | CreationTime = creationTime; 22 | LastWriteTime = lastWriteTime; 23 | ParentDirectory = parentDirectory; 24 | FullName = fullName; 25 | } 26 | 27 | public void SetPermissions(bool read, bool write, bool delete) 28 | { 29 | Readable = read; 30 | Writeable = write; 31 | Deletable = delete; 32 | } 33 | 34 | public void SetPermissionsFromACL(ACL acl) 35 | { 36 | Readable = acl.Readable; 37 | Writeable = acl.Writeable; 38 | Deletable = acl.Deletable; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /FileDiscovery/FileFinder.cs: -------------------------------------------------------------------------------- 1 | using SMBeagle.HostDiscovery; 2 | using SMBeagle.Output; 3 | using SMBeagle.ShareDiscovery; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Runtime.InteropServices; 9 | using System.Text.RegularExpressions; 10 | using System.Threading.Tasks; 11 | 12 | namespace SMBeagle.FileDiscovery 13 | { 14 | class FileFinder 15 | { 16 | Dictionary CacheACL { get; set; } = new(); 17 | IntPtr pClientContext { get; set; } 18 | HashSet FilesSentForOutput { get; set; } = new(); 19 | 20 | List _directories { get; set; } = new(); 21 | public List Directories 22 | { 23 | get 24 | { 25 | List 26 | ret = new (); 27 | 28 | ret.AddRange(_directories); 29 | 30 | foreach (Directory dir in _directories) 31 | { 32 | ret.AddRange(dir.RecursiveChildDirectories); 33 | } 34 | 35 | return ret; 36 | } 37 | } 38 | 39 | public List Files 40 | { 41 | get 42 | { 43 | return Directories 44 | .SelectMany(dir => dir.RecursiveFiles) 45 | .ToList(); 46 | } 47 | } 48 | 49 | public FileFinder(List shares, string outputDirectory, bool fetchFiles, List filePatterns, bool getPermissionsForSingleFileInDir = true, bool enumerateAcls = true, bool quiet = false, bool verbose = false, bool crossPlatform = false) 50 | { 51 | if (fetchFiles) 52 | { 53 | try 54 | { 55 | System.IO.Directory.CreateDirectory(outputDirectory); 56 | } 57 | catch 58 | { 59 | Console.WriteLine($"\nERROR: CANNOT CREATE LOOT DIR {outputDirectory}"); 60 | Environment.Exit(0); 61 | } 62 | } 63 | pClientContext = IntPtr.Zero; 64 | if (! crossPlatform) 65 | { 66 | #pragma warning disable CA1416 67 | pClientContext = WindowsHelper.GetpClientContext(); 68 | if (enumerateAcls & pClientContext == IntPtr.Zero & !quiet) 69 | { 70 | OutputHelper.WriteLine("!! Error querying user context. Failing back to a slower ACL identifier. ", 1); 71 | OutputHelper.WriteLine(" We can also no longer check if a file is deletable", 1); 72 | if (!getPermissionsForSingleFileInDir) 73 | OutputHelper.WriteLine(" It is advisable to set the fast flag and only check the ACLs of one file per directory", 1); 74 | } 75 | #pragma warning restore CA1416 76 | } 77 | 78 | foreach (Share share in shares) //TODO: dedup share by host and name 79 | { 80 | _directories.Add(new Directory(path: "", share:share) { DirectoryType = Enums.DirectoryTypeEnum.SMB }); 81 | } 82 | 83 | if (!quiet) 84 | OutputHelper.WriteLine($"6a. Enumerating all subdirectories for known paths"); 85 | 86 | bool abort = false; 87 | 88 | #nullable enable 89 | System.ConsoleCancelEventHandler handler = (object? sender, ConsoleCancelEventArgs e) => { 90 | #nullable disable 91 | if (e.SpecialKey.HasFlag(ConsoleSpecialKey.ControlBreak)) 92 | { 93 | e.Cancel = true; 94 | abort = true; 95 | Console.WriteLine("\nSKIPPING"); 96 | } 97 | else 98 | { 99 | Console.WriteLine("\nABORTED EXECUTION... Did you mean CTRL-BREAK?"); 100 | Environment.Exit(0); 101 | } 102 | }; 103 | 104 | Console.CancelKeyPress += handler; 105 | 106 | foreach (Directory dir in _directories) 107 | { 108 | OutputHelper.WriteLine($"\rEnumerating all subdirectories for '{dir.UNCPath}' - CTRL-BREAK or CTRL-PAUSE to SKIP ", 1, false); 109 | dir.FindDirectoriesRecursively(crossPlatform: crossPlatform, ref abort); 110 | abort = false; 111 | } 112 | 113 | Console.CancelKeyPress -= handler; 114 | 115 | if (!quiet) 116 | OutputHelper.WriteLine($"\r6b. Splitting large directories to optimise caching and to batch output "); 117 | 118 | SplitLargeDirectories(); 119 | 120 | if (!quiet) 121 | OutputHelper.WriteLine($"6c. Enumerating files in directories"); 122 | 123 | Console.CancelKeyPress += handler; 124 | var tasks = new List(); 125 | foreach (Directory dir in _directories) 126 | { 127 | abort = false; 128 | OutputHelper.WriteLine($"\renumerating files in '{dir.UNCPath}' - CTRL-BREAK or CTRL-PAUSE to SKIP ", 1, false); 129 | // TODO: pass in the ignored extensions from the commandline 130 | dir.FindFilesRecursively(crossPlatform: crossPlatform, ref abort, extensionsToIgnore: new List() { ".dll",".manifest",".cat" }); 131 | if (verbose) 132 | OutputHelper.WriteLine($"\rFound {dir.ChildDirectories.Count} child directories and {dir.RecursiveFiles.Count} files in '{dir.UNCPath}'",2); 133 | 134 | foreach (File file in dir.RecursiveFiles) 135 | { 136 | if (FilesSentForOutput.Add($"{dir.Share.uncPath}{file.FullName}".ToLower())) // returns True if not already present 137 | { 138 | // Cache fullnames and dont send a dupe 139 | if (enumerateAcls) 140 | FetchFilePermission(file, crossPlatform, getPermissionsForSingleFileInDir); 141 | 142 | OutputHelper.AddPayload(new Output.FileOutput(file), Enums.OutputtersEnum.File); 143 | 144 | if (fetchFiles && filePatterns.Any(pattern => Regex.IsMatch(file.Name, pattern, RegexOptions.IgnoreCase))) 145 | { 146 | tasks.Add(Task.Run(() => FetchFile(file, crossPlatform, outputDirectory))); 147 | if (crossPlatform) 148 | Task.WaitAll(tasks.ToArray()); // TODO: Generate a Client on demand so we can download in parallel - https://github.com/TalAloni/SMBLibrary/issues/59 149 | } 150 | } 151 | } 152 | 153 | dir.Clear(); 154 | CacheACL.Clear(); // Clear Cached ACLs otherwise it grows and grows 155 | } 156 | Task.WaitAll(tasks.ToArray()); 157 | Console.CancelKeyPress -= handler; 158 | OutputHelper.WriteLine($"\r file enumeration complete, {FilesSentForOutput.Count} files identified "); 159 | } 160 | 161 | private Enums.DirectoryTypeEnum DriveInfoTypeToDirectoryTypeEnum(DriveType type) 162 | { 163 | return type switch 164 | { 165 | DriveType.Fixed => Enums.DirectoryTypeEnum.LOCAL_FIXED, 166 | DriveType.CDRom => Enums.DirectoryTypeEnum.LOCAL_CDROM, 167 | DriveType.Network => Enums.DirectoryTypeEnum.LOCAL_NETWORK, 168 | DriveType.Removable => Enums.DirectoryTypeEnum.LOCAL_REMOVEABLE, 169 | _ => Enums.DirectoryTypeEnum.UNKNOWN 170 | }; 171 | } 172 | 173 | //TODO: Reimplement at some point 174 | /*private List GetLocalDriveDirectories() 175 | { 176 | // Create dummy sahre 177 | Share dummyShare = new Share(new HostDiscovery.Host("localhost"), "", Enums.ShareTypeEnum.DISK); 178 | return DriveInfo 179 | .GetDrives() 180 | .Where(drive => drive.IsReady) 181 | .Select(drive => new Directory(drive.Name, share: dummyShare) { DirectoryType = DriveInfoTypeToDirectoryTypeEnum(drive.DriveType) }) 182 | .ToList(); 183 | }*/ 184 | 185 | private void SplitLargeDirectories(int maxChildCount = 20) 186 | { 187 | List 188 | oversizedDirectories = _directories.Where(item => item.RecursiveChildDirectories.Count > maxChildCount).ToList(); 189 | 190 | while (oversizedDirectories.Count > 0) 191 | { 192 | foreach (Directory dir in oversizedDirectories) 193 | { 194 | _directories.Remove(dir); 195 | _directories.AddRange(dir.ChildDirectories); 196 | } 197 | 198 | oversizedDirectories = _directories.Where(item => item.RecursiveChildDirectories.Count > maxChildCount).ToList(); 199 | } 200 | } 201 | 202 | private void FetchFilePermission(File file, bool crossPlatform, bool useCache = true) 203 | { 204 | if (useCache && CacheACL.Keys.Contains(file.ParentDirectory.Path)) // If we should use cache and cache has a hit 205 | file.SetPermissionsFromACL(CacheACL[file.ParentDirectory.Path]); 206 | else 207 | { 208 | ACL permissions; 209 | if (!crossPlatform) 210 | #pragma warning disable CA1416 211 | { 212 | if (pClientContext != IntPtr.Zero) 213 | permissions = WindowsHelper.ResolvePermissions(file.FullName, pClientContext); 214 | 215 | else 216 | permissions = WindowsHelper.ResolvePermissionsSlow(file.FullName); 217 | 218 | } 219 | #pragma warning restore CA1416 220 | else 221 | { 222 | permissions = CrossPlatformHelper.ResolvePermissions(file); 223 | } 224 | file.SetPermissionsFromACL(permissions); 225 | 226 | if (useCache) 227 | CacheACL[file.ParentDirectory.Path] = permissions; 228 | } 229 | } 230 | 231 | private void FetchFile(File file, bool crossPlatform, string outputDirectory) 232 | { 233 | byte[] fileBytes; 234 | string filename; 235 | if (!crossPlatform) 236 | #pragma warning disable CA1416 237 | { 238 | // TODO: Add windows method 239 | filename = $"{outputDirectory}{Path.DirectorySeparatorChar}{file.FullName}".Replace("\\", "_").Replace("/", "_"); 240 | filename = $"{outputDirectory}{Path.DirectorySeparatorChar}{filename}"; 241 | WindowsHelper.RetrieveFile(file, filename); 242 | } 243 | #pragma warning restore CA1416 244 | else 245 | { 246 | filename = $"{file.ParentDirectory.Share.uncPath}{file.FullName}".Replace("\\", "_").Replace("/", "_"); 247 | filename = $"{outputDirectory}{Path.DirectorySeparatorChar}{filename}"; 248 | CrossPlatformHelper.RetrieveFile(file, filename); 249 | } 250 | 251 | } 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /FileDiscovery/Output/FileOutput.cs: -------------------------------------------------------------------------------- 1 | using SMBeagle.Output; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace SMBeagle.FileDiscovery.Output 9 | { 10 | class FileOutput : IOutputPayload 11 | { 12 | public string Name { get; set; } 13 | public string Host { get; set; } 14 | public string Extension { get; set; } 15 | public string Username { get; set; } 16 | public string Hostname { get; set; } 17 | public string UNCDirectory { get; set; } 18 | public DateTime CreationTime { get; set; } 19 | public DateTime LastWriteTime { get; set; } 20 | public bool Readable { get; set; } 21 | public bool Writeable { get; set; } 22 | public bool Deletable { get; set; } 23 | public Enums.DirectoryTypeEnum DirectoryType { get; set; } 24 | public string Base { get; set; } 25 | public FileOutput(File file) 26 | { 27 | Name = file.Name.ToLower(); 28 | Host = file.ParentDirectory.Share.Host.Address; 29 | Extension = file.Extension.TrimStart('.').ToLower(); 30 | UNCDirectory = file.ParentDirectory.UNCPath.ToLower(); 31 | CreationTime = file.CreationTime; 32 | LastWriteTime = file.LastWriteTime; 33 | Readable = file.Readable; 34 | Writeable = file.Writeable; 35 | Deletable = file.Deletable; 36 | DirectoryType = file.ParentDirectory.Base.DirectoryType; 37 | Base = file.ParentDirectory.Share.uncPath; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /FileDiscovery/WindowsHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Runtime.InteropServices; 4 | using System.Runtime.Versioning; 5 | using System.Security.Principal; 6 | 7 | namespace SMBeagle.FileDiscovery 8 | { 9 | [SupportedOSPlatform("windows")] 10 | class WindowsHelper 11 | { 12 | [DllImport("advapi32.dll", SetLastError = true)] 13 | static extern uint GetEffectiveRightsFromAcl(IntPtr pDacl, ref TRUSTEE pTrustee, ref ACCESS_MASK pAccessRights); 14 | 15 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto, Pack = 4)] 16 | struct TRUSTEE 17 | { 18 | IntPtr pMultipleTrustee; // must be null 19 | public int MultipleTrusteeOperation; 20 | public TRUSTEE_FORM TrusteeForm; 21 | public TRUSTEE_TYPE TrusteeType; 22 | [MarshalAs(UnmanagedType.LPStr)] 23 | public string ptstrName; 24 | } 25 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto, Pack = 4)] 26 | public struct LUID 27 | { 28 | public uint LowPart; 29 | public int HighPart; 30 | } 31 | 32 | [StructLayout(LayoutKind.Sequential)] 33 | public struct AUTHZ_ACCESS_REQUEST 34 | { 35 | public int DesiredAccess; 36 | public byte[] PrincipalSelfSid; 37 | public OBJECT_TYPE_LIST[] ObjectTypeList; 38 | public int ObjectTypeListLength; 39 | public IntPtr OptionalArguments; 40 | }; 41 | [StructLayout(LayoutKind.Sequential)] 42 | public struct OBJECT_TYPE_LIST 43 | { 44 | OBJECT_TYPE_LEVEL Level; 45 | int Sbz; 46 | IntPtr ObjectType; 47 | }; 48 | 49 | [StructLayout(LayoutKind.Sequential)] 50 | public struct AUTHZ_ACCESS_REPLY 51 | { 52 | public int ResultListLength; 53 | public IntPtr GrantedAccessMask; 54 | public IntPtr SaclEvaluationResults; 55 | public IntPtr Error; 56 | }; 57 | 58 | public enum OBJECT_TYPE_LEVEL : int 59 | { 60 | ACCESS_OBJECT_GUID = 0, 61 | ACCESS_PROPERTY_SET_GUID = 1, 62 | ACCESS_PROPERTY_GUID = 2, 63 | ACCESS_MAX_LEVEL = 4 64 | }; 65 | enum TRUSTEE_FORM 66 | { 67 | TRUSTEE_IS_SID, 68 | TRUSTEE_IS_NAME, 69 | TRUSTEE_BAD_FORM, 70 | TRUSTEE_IS_OBJECTS_AND_SID, 71 | TRUSTEE_IS_OBJECTS_AND_NAME 72 | } 73 | 74 | enum AUTHZ_RM_FLAG : uint 75 | { 76 | AUTHZ_RM_FLAG_NO_AUDIT = 1, 77 | AUTHZ_RM_FLAG_INITIALIZE_UNDER_IMPERSONATION = 2, 78 | AUTHZ_RM_FLAG_NO_CENTRAL_ACCESS_POLICIES = 4, 79 | } 80 | 81 | enum TRUSTEE_TYPE 82 | { 83 | TRUSTEE_IS_UNKNOWN, 84 | TRUSTEE_IS_USER, 85 | TRUSTEE_IS_GROUP, 86 | TRUSTEE_IS_DOMAIN, 87 | TRUSTEE_IS_ALIAS, 88 | TRUSTEE_IS_WELL_KNOWN_GROUP, 89 | TRUSTEE_IS_DELETED, 90 | TRUSTEE_IS_INVALID, 91 | TRUSTEE_IS_COMPUTER 92 | } 93 | 94 | [DllImport("advapi32.dll", CharSet = CharSet.Auto)] 95 | static extern uint GetNamedSecurityInfo( 96 | string pObjectName, 97 | SE_OBJECT_TYPE ObjectType, 98 | SECURITY_INFORMATION SecurityInfo, 99 | out IntPtr pSidOwner, 100 | out IntPtr pSidGroup, 101 | out IntPtr pDacl, 102 | out IntPtr pSacl, 103 | out IntPtr pSecurityDescriptor); 104 | [DllImport("authz.dll", SetLastError = true, CallingConvention = CallingConvention.StdCall, EntryPoint = "AuthzInitializeContextFromSid", CharSet = CharSet.Unicode)] 105 | static extern public bool AuthzInitializeContextFromSid( 106 | int Flags, 107 | IntPtr UserSid, 108 | IntPtr AuthzResourceManager, 109 | IntPtr pExpirationTime, 110 | LUID Identitifier, 111 | IntPtr DynamicGroupArgs, 112 | out IntPtr pAuthzClientContext 113 | ); 114 | 115 | 116 | 117 | [DllImport("authz.dll", SetLastError = true, CallingConvention = CallingConvention.StdCall, EntryPoint = "AuthzInitializeResourceManager", CharSet = CharSet.Unicode)] 118 | static extern public bool AuthzInitializeResourceManager( 119 | int flags, 120 | IntPtr pfnAccessCheck, 121 | IntPtr pfnComputeDynamicGroups, 122 | IntPtr pfnFreeDynamicGroups, 123 | string name, 124 | out IntPtr rm 125 | ); 126 | [DllImport("authz.dll", EntryPoint = "AuthzAccessCheck", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)] 127 | private static extern bool AuthzAccessCheck(int flags, 128 | IntPtr hAuthzClientContext, 129 | ref AUTHZ_ACCESS_REQUEST pRequest, 130 | IntPtr AuditEvent, 131 | IntPtr pSecurityDescriptor, 132 | byte[] OptionalSecurityDescriptorArray, 133 | int OptionalSecurityDescriptorCount, 134 | ref AUTHZ_ACCESS_REPLY pReply, 135 | out IntPtr phAccessCheckResults); 136 | 137 | 138 | [DllImport("authz.dll", EntryPoint = "AuthzFreeResourceManager", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)] 139 | private static extern bool AuthzFreeResourceManager( IntPtr hAuthzResourceManager ); 140 | 141 | 142 | enum ACCESS_MASK : uint 143 | { 144 | FILE_TRAVERSE = 0x20, 145 | FILE_LIST_DIRECTORY = 0x1, 146 | FILE_READ_DATA = 0x1, 147 | FILE_READ_ATTRIBUTES = 0x80, 148 | FILE_READ_EA = 0x8, 149 | FILE_ADD_FILE = 0x2, 150 | FILE_WRITE_DATA = 0x2, 151 | FILE_ADD_SUBDIRECTORY = 0x4, 152 | FILE_APPEND_DATA = 0x4, 153 | FILE_WRITE_ATTRIBUTES = 0x100, 154 | FILE_WRITE_EA = 0x10, 155 | FILE_DELETE_CHILD = 0x40, 156 | DELETE = 0x10000, 157 | READ_CONTROL = 0x20000, 158 | WRITE_DAC = 0x40000, 159 | WRITE_OWNER = 0x80000, 160 | 161 | 162 | ////////FILE_EXECUTE =0x20, 163 | } 164 | 165 | [Flags] 166 | enum SECURITY_INFORMATION : uint 167 | { 168 | OWNER_SECURITY_INFORMATION = 0x00000001, 169 | GROUP_SECURITY_INFORMATION = 0x00000002, 170 | DACL_SECURITY_INFORMATION = 0x00000004, 171 | SACL_SECURITY_INFORMATION = 0x00000008, 172 | UNPROTECTED_SACL_SECURITY_INFORMATION = 0x10000000, 173 | UNPROTECTED_DACL_SECURITY_INFORMATION = 0x20000000, 174 | PROTECTED_SACL_SECURITY_INFORMATION = 0x40000000, 175 | PROTECTED_DACL_SECURITY_INFORMATION = 0x80000000 176 | } 177 | 178 | enum SE_OBJECT_TYPE 179 | { 180 | SE_UNKNOWN_OBJECT_TYPE = 0, 181 | SE_FILE_OBJECT, 182 | SE_SERVICE, 183 | SE_PRINTER, 184 | SE_REGISTRY_KEY, 185 | SE_LMSHARE, 186 | SE_KERNEL_OBJECT, 187 | SE_WINDOW_OBJECT, 188 | SE_DS_OBJECT, 189 | SE_DS_OBJECT_ALL, 190 | SE_PROVIDER_DEFINED_OBJECT, 191 | SE_WMIGUID_OBJECT, 192 | SE_REGISTRY_WOW64_32KEY 193 | } 194 | 195 | public static IntPtr GetUserSid() 196 | { 197 | string username = WindowsIdentity.GetCurrent().Name; 198 | NTAccount ac = new NTAccount(username); 199 | SecurityIdentifier sid = (SecurityIdentifier)ac.Translate(typeof(SecurityIdentifier)); 200 | byte[] bytes = new byte[sid.BinaryLength]; 201 | sid.GetBinaryForm(bytes, 0); 202 | String _psUserSid = ""; 203 | foreach (byte si in bytes) 204 | { 205 | _psUserSid += si; 206 | } 207 | IntPtr UserSid = Marshal.AllocHGlobal(bytes.Length); 208 | Marshal.Copy(bytes, 0, UserSid, bytes.Length); 209 | return UserSid; 210 | } 211 | 212 | public static IntPtr GetpClientContext() 213 | { 214 | IntPtr sid = WindowsHelper.GetUserSid(); 215 | IntPtr hManager = IntPtr.Zero; 216 | AuthzInitializeResourceManager(1, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, null, out hManager); 217 | IntPtr pClientContext = IntPtr.Zero; 218 | LUID unusedSid = new LUID(); 219 | AuthzInitializeContextFromSid(0, sid, hManager, IntPtr.Zero, unusedSid, IntPtr.Zero, out pClientContext); 220 | return pClientContext; 221 | } 222 | 223 | public static ACL ResolvePermissions(string path, IntPtr pClientContext) 224 | { 225 | 226 | ACL acl = new ACL() { Readable = false, Writeable = false, Deletable = false }; 227 | IntPtr pSidOwner, pSidGroup, pDacl, pSacl, pSecurityDescriptor; 228 | 229 | uint ret = GetNamedSecurityInfo(path, 230 | SE_OBJECT_TYPE.SE_FILE_OBJECT, 231 | SECURITY_INFORMATION.DACL_SECURITY_INFORMATION | SECURITY_INFORMATION.OWNER_SECURITY_INFORMATION | SECURITY_INFORMATION.GROUP_SECURITY_INFORMATION, 232 | out pSidOwner, out pSidGroup, out pDacl, out pSacl, out pSecurityDescriptor); 233 | 234 | 235 | 236 | AUTHZ_ACCESS_REQUEST request = new AUTHZ_ACCESS_REQUEST(); 237 | request.DesiredAccess = 0x02000000; 238 | request.PrincipalSelfSid = null; 239 | request.ObjectTypeList = null; 240 | request.ObjectTypeListLength = 0; 241 | request.OptionalArguments = IntPtr.Zero; 242 | 243 | AUTHZ_ACCESS_REPLY reply = new AUTHZ_ACCESS_REPLY(); 244 | reply.GrantedAccessMask = IntPtr.Zero; 245 | reply.ResultListLength = 0; 246 | reply.SaclEvaluationResults = IntPtr.Zero; 247 | IntPtr AccessReply = IntPtr.Zero; 248 | reply.Error = Marshal.AllocHGlobal(1020); 249 | reply.GrantedAccessMask = Marshal.AllocHGlobal(sizeof(uint)); 250 | reply.ResultListLength = 1; 251 | if (AuthzAccessCheck(0, pClientContext, ref request, IntPtr.Zero, pSecurityDescriptor, null, 0, ref reply, out AccessReply)) 252 | { 253 | int granted_access = Marshal.ReadInt32(reply.GrantedAccessMask); 254 | ACCESS_MASK mask = (ACCESS_MASK)granted_access; 255 | if ((mask & ACCESS_MASK.DELETE) > 0) 256 | acl.Deletable = true; 257 | if ((mask & ACCESS_MASK.FILE_READ_DATA) > 0) 258 | acl.Readable = true; 259 | if ((mask & ACCESS_MASK.FILE_WRITE_DATA) > 0) 260 | acl.Writeable = true; 261 | } 262 | FreePointerH(AccessReply); 263 | FreePointerH(reply.GrantedAccessMask); 264 | FreePointerH(reply.SaclEvaluationResults); 265 | FreePointerH(reply.Error); 266 | FreePointerC(pSacl); 267 | 268 | FreePointerC(pSecurityDescriptor); 269 | return acl; 270 | } 271 | 272 | 273 | public static ACL ResolvePermissionsSlow(string path) 274 | { 275 | ACL acl = new(); 276 | try 277 | { 278 | new FileStream(path, FileMode.Open, FileAccess.Read).Dispose(); 279 | acl.Readable = true; 280 | } 281 | catch 282 | { 283 | // An error is expected if not readable 284 | } 285 | try 286 | { 287 | new FileStream(path, FileMode.Open, FileAccess.Write).Dispose(); 288 | acl.Writeable = true; 289 | } 290 | catch 291 | { 292 | // An error is expected if not writeable 293 | } 294 | return acl; 295 | } 296 | 297 | static void FreePointerH(IntPtr pointer) 298 | { 299 | if (pointer != IntPtr.Zero) 300 | Marshal.FreeHGlobal(pointer); 301 | } 302 | 303 | static void FreePointerC(IntPtr pointer ) 304 | { 305 | if (pointer != IntPtr.Zero) 306 | Marshal.FreeCoTaskMem(pointer); 307 | } 308 | public static void RetrieveFile(File file, string outputPath) 309 | { 310 | System.IO.File.Copy(file.FullName, outputPath, true); 311 | } 312 | 313 | } 314 | } -------------------------------------------------------------------------------- /HostDiscovery/Host.cs: -------------------------------------------------------------------------------- 1 | using SMBeagle.ShareDiscovery; 2 | using SMBLibrary.Client; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Net.Sockets; 6 | 7 | namespace SMBeagle.HostDiscovery 8 | { 9 | class Host 10 | { 11 | public const int PORT_MAX_WAIT_MS = 500; 12 | public string Address { get; set; } 13 | public bool SMBAvailable { get { return _SMBAvailable; } } 14 | private bool _SMBAvailable { get; set; } 15 | public List Shares { get; set; } = new(); 16 | #nullable enable 17 | public ISMBClient? Client { get; set; } = null; 18 | #nullable disable 19 | public int ShareCount { get { return Shares.Count; } } 20 | public Host(string address) 21 | { 22 | Address = address; 23 | } 24 | 25 | public void TestSMB() 26 | { 27 | _SMBAvailable = HostRespondsToTCP445(); 28 | } 29 | 30 | bool HostRespondsToTCP445() 31 | { 32 | using TcpClient t = new(); 33 | 34 | try 35 | { 36 | if (t.ConnectAsync(Address, 445).Wait(PORT_MAX_WAIT_MS)) 37 | return true; // We connected 38 | 39 | return false; // We timedout 40 | } 41 | catch 42 | { 43 | return false; // We hit an error 44 | } 45 | } 46 | 47 | public override string ToString() 48 | { 49 | return Address; 50 | } 51 | 52 | 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /HostDiscovery/HostFinder.cs: -------------------------------------------------------------------------------- 1 | using SMBeagle.NetworkDiscovery; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading; 5 | 6 | namespace SMBeagle.HostDiscovery 7 | { 8 | class HostFinder 9 | { 10 | private List _Hosts = new List(); 11 | public List ReachableHosts { get { return _Hosts.Where(item => item.SMBAvailable).ToList(); } } 12 | public List HostsWithShares { get { return ReachableHosts.Where(item => item.ShareCount > 0).ToList(); } } 13 | private List _Candidates = new List(); 14 | public HostFinder(List knownHostAddresses, List knownNetworks, List blacklistedAddresses ) 15 | { 16 | BuildCandidateList(knownHostAddresses, knownNetworks); 17 | foreach (string candidate in _Candidates) 18 | { 19 | if (blacklistedAddresses.Contains(candidate)) 20 | continue; // Dont store blacklisted addresses 21 | AddHost(candidate); 22 | } 23 | InititiateSMBTestsForHosts(); 24 | } 25 | 26 | private void InititiateSMBTestsForHosts(int chunkSize = 5000) 27 | { 28 | for (int x = 0; x <= _Hosts.Count / chunkSize; x++) 29 | { 30 | foreach (Host host in _Hosts.Skip(x * chunkSize).Take(chunkSize)) 31 | { 32 | Thread t = new(() => host.TestSMB()); 33 | t.Start(); 34 | } 35 | // Wait for max scan time 36 | Thread.Sleep(Host.PORT_MAX_WAIT_MS); 37 | } 38 | } 39 | 40 | public void AddHost(Host host) 41 | { 42 | if (_Hosts.Any(item => item.Address == host.Address)) 43 | return; 44 | _Hosts.Add(host); 45 | } 46 | 47 | public void AddHost(string address) 48 | { 49 | AddHost(new Host(address)); 50 | } 51 | 52 | public void BuildCandidateList(List knownHostAddresses, List knownNetworks) 53 | { 54 | List candidates = new List(); 55 | foreach (Network network in knownNetworks.Where(item => item.IPVersion == 4)) 56 | { 57 | foreach (string address in network.AddressList) 58 | candidates.Add(address); 59 | } 60 | foreach (string address in knownHostAddresses) 61 | { 62 | candidates.Add(address); 63 | } 64 | _Candidates = new HashSet(candidates).ToList(); 65 | } 66 | 67 | 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Kibana/README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | This section will explain how to import the SMBeagle Kibana pattern and Dashboard. 3 | 4 | Using the import actions, you can create objects in a Kibana instances. You can import multiple objects in a single operation. 5 | 6 | SMBeagle uses a custom Kibana pattern, which allows dynamic creation, updates and deletion of Elastic indexes, and a custom dashboard, for easy consumption of the SMBeagle data. 7 | 8 | ## Pre-requisites 9 | You will need to install [Docker desktop](https://www.docker.com/products/docker-desktop), and set up a folder to host your presistant elastic data. 10 | 11 | ### Persistence 12 | In order to persist data you will need to create a local directory to store the elastic indices. To create the directory for elastic to store persistent data, you will need to do one of the following. 13 | 14 | For Linux 15 | ``` 16 | /bin/bash 17 | mkdir ~/elasticsearch 18 | chown -R 1000:1000 ~/elasticsearch 19 | ``` 20 | 21 | For Windows 22 | ``` 23 | cmd.exe 24 | mkdir c:\elasticsearch 25 | ``` 26 | 27 | Inside the docker-compose.yml file we have preconfigured to C:\elasticsearch, but to customise it edit the file and change the following line; 28 | 29 | ``` 30 | elasticsearch: 31 | image: docker.elastic.co/elasticsearch/elasticsearch:7.14.2 32 | ... 33 | volumes: 34 | # - ~/elasticsearch:/usr/share/elasticsearch/data # Linux mapping 35 | - C:\elasticsearch:/usr/share/elasticsearch/data # Windows mapping 36 | ``` 37 | 38 | The volume configuration is split in to two parts {local folder}:{docker folder}, only edit the local folder section. 39 | 40 | ### Networking 41 | In our quick start up guide we will use a custom docker-compose file, to stand up elastic and kibana service. Our docker-compose service will only expose TCP 9200 (elasticsearch) and TCP 5601 (Kibana) to your localhost (127.0.0.1). 42 | 43 | The network can be altered to be exposed to your network by editing the docker-compose.yml file 44 | 45 | To expose elastic change **127.0.0.1:9200:9200** to **0.0.0.0:9200:9200** 46 | ``` 47 | services: 48 | elasticsearch: 49 | image: docker.elastic.co/elasticsearch/elasticsearch:7.14.2 50 | ... 51 | ports: 52 | - "0.0.0.0:9200:9200" 53 | ``` 54 | 55 | To expose kibana change **127.0.0.1:9200:9200** to **0.0.0.0:5601:5601** 56 | ``` 57 | kibana: 58 | image: docker.elastic.co/kibana/kibana:7.14.2 59 | ... 60 | ports: 61 | - "0.0.0.0:5601:5601" 62 | ``` 63 | 64 | # Quick start for Elasticsearch on docker 65 | ## Deploy Elasticsearch and Kibana 66 | Once you have installed Docker desktop and setup your persistence folder. You can now start up elastic with one simple command 67 | 68 | 1. Open a command prompt or bash terminal 69 | 2. Change directory to the folder containing the docker-compose.yml file 70 | 3. Run the following 71 | 72 | ``` 73 | docker-compose up -d 74 | ``` 75 | 76 | ## Test Elasticsearch is up and running 77 | To test elasticsearch, run the following command; 78 | 79 | ``` 80 | curl 127.0.0.1:9200 81 | ``` 82 | 83 | To test kibana open a browser and connect to; 84 | 85 | http://127.0.0.1:5601 86 | 87 | ## Deploy Kibana dashboard 88 | Once you have deployed elastic, you can how install the elastic dashboards and start collecting data. 89 | 90 | 1. Download the SMBeagle **export.ndjson** file 91 | 2. Open your Kibana web management console e.g. http://127.0.0.1:5601/ 92 | 3. Click on **Stack Management** > **Saved Objects** 93 | 4. Click on **import** 94 | 5. Click on the **import** under **select a file to import** 95 | 6. Highlight the **export.ndjson** file and click open 96 | 7. Click on the **Import** button at the bottom of the screen 97 | 8. Click on **Done** 98 | -------------------------------------------------------------------------------- /Kibana/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | elasticsearch: 5 | image: docker.elastic.co/elasticsearch/elasticsearch:7.15 6 | container_name: elasticsearch 7 | restart: 'no' 8 | hostname: elasticsearch 9 | ports: 10 | - "127.0.0.1:9200:9200" 11 | volumes: 12 | # - ~/elasticsearch:/usr/share/elasticsearch/data # Linux mapping 13 | - C:\elasticsearch:/usr/share/elasticsearch/data # Windows mapping 14 | environment: 15 | ES_JAVA_OPTS: '-Xms4g -Xmx4g' 16 | cluster.name: elasticsearch 17 | bootstrap.memory_lock: 'true' 18 | network.host: 0.0.0.0 19 | http.port: 9200 20 | discovery.type: 'single-node' 21 | indices.query.bool.max_clause_count: 8192 22 | search.max_buckets: 250000 23 | 24 | kibana: 25 | image: docker.elastic.co/kibana/kibana:7.15 26 | container_name: kibana 27 | restart: 'no' 28 | hostname: kibana 29 | depends_on: 30 | - elasticsearch 31 | ports: 32 | - "127.0.0.1:5601:5601" 33 | environment: 34 | SERVER_HOST: 0.0.0.0 35 | SERVER_PORT: 5601 36 | ELASTICSEARCH_HOSTS: "http://elasticsearch:9200" 37 | ELASTICSEARCH_REQUESTTIMEOUT: 132000 38 | ELASTICSEARCH_SHARDTIMEOUT: 120000 39 | LOGGING_DEST: stdout 40 | -------------------------------------------------------------------------------- /Kibana/export.ndjson: -------------------------------------------------------------------------------- 1 | {"attributes":{"fieldAttrs":"{}","fields":"[]","runtimeFieldMap":"{}","timeFieldName":"File.CreationTime","title":"smbeagle*","typeMeta":"{}"},"coreMigrationVersion":"7.15.0","id":"2b19d650-31e8-11ec-8fdb-bdb52ae291fb","migrationVersion":{"index-pattern":"7.11.0"},"references":[],"sort":[1658043614997,4],"type":"index-pattern","updated_at":"2022-07-17T07:40:14.997Z","version":"WzE3LDFd"} 2 | {"attributes":{"description":"","hits":0,"kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}"},"optionsJSON":"{\"useMargins\":true,\"syncColors\":false,\"hidePanelTitles\":false}","panelsJSON":"[{\"version\":\"7.15.0\",\"type\":\"visualization\",\"gridData\":{\"x\":0,\"y\":0,\"w\":47,\"h\":5,\"i\":\"cc280b2b-881c-4e45-9b7d-ff1649d590e7\"},\"panelIndex\":\"cc280b2b-881c-4e45-9b7d-ff1649d590e7\",\"embeddableConfig\":{\"savedVis\":{\"title\":\"\",\"description\":\"\",\"type\":\"markdown\",\"params\":{\"fontSize\":12,\"openLinksInNewTab\":false,\"markdown\":\"# Summary\"},\"uiState\":{},\"data\":{\"aggs\":[],\"searchSource\":{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}}},\"hidePanelTitles\":true,\"enhancements\":{}}},{\"version\":\"7.15.0\",\"type\":\"lens\",\"gridData\":{\"x\":0,\"y\":5,\"w\":11,\"h\":6,\"i\":\"2c218684-e98e-46ac-a255-b70736fa8ef6\"},\"panelIndex\":\"2c218684-e98e-46ac-a255-b70736fa8ef6\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsMetric\",\"type\":\"lens\",\"references\":[{\"type\":\"index-pattern\",\"id\":\"2b19d650-31e8-11ec-8fdb-bdb52ae291fb\",\"name\":\"indexpattern-datasource-current-indexpattern\"},{\"type\":\"index-pattern\",\"id\":\"2b19d650-31e8-11ec-8fdb-bdb52ae291fb\",\"name\":\"indexpattern-datasource-layer-7ff74256-fcbd-4520-bebf-7f136af63cf4\"}],\"state\":{\"visualization\":{\"layerId\":\"7ff74256-fcbd-4520-bebf-7f136af63cf4\",\"accessor\":\"35143716-ddb3-42f1-8d5f-d3972094c6e5\",\"layerType\":\"data\"},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"indexpattern\":{\"layers\":{\"7ff74256-fcbd-4520-bebf-7f136af63cf4\":{\"columns\":{\"35143716-ddb3-42f1-8d5f-d3972094c6e5\":{\"label\":\"Unique Directories\",\"dataType\":\"number\",\"operationType\":\"unique_count\",\"scale\":\"ratio\",\"sourceField\":\"File.UNCDirectory.keyword\",\"isBucketed\":false,\"customLabel\":true}},\"columnOrder\":[\"35143716-ddb3-42f1-8d5f-d3972094c6e5\"],\"incompleteColumns\":{}}}}}}},\"hidePanelTitles\":true,\"enhancements\":{}}},{\"version\":\"7.15.0\",\"type\":\"lens\",\"gridData\":{\"x\":11,\"y\":5,\"w\":12,\"h\":6,\"i\":\"3c187785-39ca-4649-beff-e952f84c73c6\"},\"panelIndex\":\"3c187785-39ca-4649-beff-e952f84c73c6\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsMetric\",\"type\":\"lens\",\"references\":[{\"id\":\"2b19d650-31e8-11ec-8fdb-bdb52ae291fb\",\"name\":\"indexpattern-datasource-current-indexpattern\",\"type\":\"index-pattern\"},{\"id\":\"2b19d650-31e8-11ec-8fdb-bdb52ae291fb\",\"name\":\"indexpattern-datasource-layer-342c5503-0e6b-413f-a6e4-395f4c5738dc\",\"type\":\"index-pattern\"}],\"state\":{\"visualization\":{\"layerId\":\"342c5503-0e6b-413f-a6e4-395f4c5738dc\",\"accessor\":\"7b183873-ef4e-41dd-a233-32ecbd19b93c\",\"layerType\":\"data\"},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"indexpattern\":{\"layers\":{\"342c5503-0e6b-413f-a6e4-395f4c5738dc\":{\"columns\":{\"7b183873-ef4e-41dd-a233-32ecbd19b93c\":{\"label\":\"Unique File Shares\",\"dataType\":\"number\",\"operationType\":\"unique_count\",\"scale\":\"ratio\",\"sourceField\":\"File.Base.keyword\",\"isBucketed\":false,\"customLabel\":true}},\"columnOrder\":[\"7b183873-ef4e-41dd-a233-32ecbd19b93c\"],\"incompleteColumns\":{}}}}}}},\"hidePanelTitles\":true,\"enhancements\":{}}},{\"version\":\"7.15.0\",\"type\":\"lens\",\"gridData\":{\"x\":23,\"y\":5,\"w\":12,\"h\":6,\"i\":\"a6401f2f-b3cd-4f43-83b9-d0d559c2bfe7\"},\"panelIndex\":\"a6401f2f-b3cd-4f43-83b9-d0d559c2bfe7\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsMetric\",\"type\":\"lens\",\"references\":[{\"id\":\"2b19d650-31e8-11ec-8fdb-bdb52ae291fb\",\"name\":\"indexpattern-datasource-current-indexpattern\",\"type\":\"index-pattern\"},{\"id\":\"2b19d650-31e8-11ec-8fdb-bdb52ae291fb\",\"name\":\"indexpattern-datasource-layer-84c522d2-d32a-48f1-9230-cbcdd086bb65\",\"type\":\"index-pattern\"}],\"state\":{\"visualization\":{\"layerId\":\"84c522d2-d32a-48f1-9230-cbcdd086bb65\",\"accessor\":\"91cf2c4a-0749-4a4c-96f8-c64cc2e6818f\",\"layerType\":\"data\"},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"indexpattern\":{\"layers\":{\"84c522d2-d32a-48f1-9230-cbcdd086bb65\":{\"columns\":{\"91cf2c4a-0749-4a4c-96f8-c64cc2e6818f\":{\"label\":\"Unique File Names\",\"dataType\":\"number\",\"operationType\":\"unique_count\",\"scale\":\"ratio\",\"sourceField\":\"File.Name.keyword\",\"isBucketed\":false,\"customLabel\":true}},\"columnOrder\":[\"91cf2c4a-0749-4a4c-96f8-c64cc2e6818f\"],\"incompleteColumns\":{}}}}}}},\"hidePanelTitles\":true,\"enhancements\":{}}},{\"version\":\"7.15.0\",\"type\":\"lens\",\"gridData\":{\"x\":35,\"y\":5,\"w\":12,\"h\":6,\"i\":\"da33a7b7-05a1-4186-b0f6-d343e49befe1\"},\"panelIndex\":\"da33a7b7-05a1-4186-b0f6-d343e49befe1\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsMetric\",\"type\":\"lens\",\"references\":[{\"id\":\"2b19d650-31e8-11ec-8fdb-bdb52ae291fb\",\"name\":\"indexpattern-datasource-current-indexpattern\",\"type\":\"index-pattern\"},{\"id\":\"2b19d650-31e8-11ec-8fdb-bdb52ae291fb\",\"name\":\"indexpattern-datasource-layer-42c75835-7aff-4c8c-bbb0-0be4bf175acd\",\"type\":\"index-pattern\"}],\"state\":{\"visualization\":{\"layerId\":\"42c75835-7aff-4c8c-bbb0-0be4bf175acd\",\"accessor\":\"ee89511c-4794-4dfe-bf6b-c10b718e503d\",\"layerType\":\"data\"},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"indexpattern\":{\"layers\":{\"42c75835-7aff-4c8c-bbb0-0be4bf175acd\":{\"columns\":{\"ee89511c-4794-4dfe-bf6b-c10b718e503d\":{\"label\":\"Unique File Ext\",\"dataType\":\"number\",\"operationType\":\"unique_count\",\"scale\":\"ratio\",\"sourceField\":\"File.Extension.keyword\",\"isBucketed\":false,\"customLabel\":true}},\"columnOrder\":[\"ee89511c-4794-4dfe-bf6b-c10b718e503d\"],\"incompleteColumns\":{}}}}}}},\"hidePanelTitles\":true,\"enhancements\":{}}},{\"version\":\"7.15.0\",\"type\":\"lens\",\"gridData\":{\"x\":0,\"y\":11,\"w\":23,\"h\":15,\"i\":\"50167deb-ac78-4a95-9762-7215948d1c50\"},\"panelIndex\":\"50167deb-ac78-4a95-9762-7215948d1c50\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsXY\",\"type\":\"lens\",\"references\":[{\"id\":\"2b19d650-31e8-11ec-8fdb-bdb52ae291fb\",\"name\":\"indexpattern-datasource-current-indexpattern\",\"type\":\"index-pattern\"},{\"id\":\"2b19d650-31e8-11ec-8fdb-bdb52ae291fb\",\"name\":\"indexpattern-datasource-layer-76982cc4-fc81-4614-bee1-283454f055ff\",\"type\":\"index-pattern\"}],\"state\":{\"visualization\":{\"legend\":{\"isVisible\":true,\"position\":\"right\"},\"valueLabels\":\"hide\",\"fittingFunction\":\"None\",\"yLeftExtent\":{\"mode\":\"full\"},\"yRightExtent\":{\"mode\":\"full\"},\"axisTitlesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"tickLabelsVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"labelsOrientation\":{\"x\":0,\"yLeft\":0,\"yRight\":0},\"gridlinesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"preferredSeriesType\":\"bar_stacked\",\"layers\":[{\"layerId\":\"76982cc4-fc81-4614-bee1-283454f055ff\",\"accessors\":[\"5df59fa2-37b5-4769-974a-523735722fe9\"],\"position\":\"top\",\"seriesType\":\"bar_stacked\",\"showGridlines\":false,\"layerType\":\"data\",\"xAccessor\":\"3b51da1c-d1eb-44a2-9331-8f7a96a15fa4\"}]},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"indexpattern\":{\"layers\":{\"76982cc4-fc81-4614-bee1-283454f055ff\":{\"columns\":{\"3b51da1c-d1eb-44a2-9331-8f7a96a15fa4\":{\"label\":\"Top values of File.Base.keyword\",\"dataType\":\"string\",\"operationType\":\"terms\",\"scale\":\"ordinal\",\"sourceField\":\"File.Base.keyword\",\"isBucketed\":true,\"params\":{\"size\":15,\"orderBy\":{\"type\":\"column\",\"columnId\":\"5df59fa2-37b5-4769-974a-523735722fe9\"},\"orderDirection\":\"desc\",\"otherBucket\":true,\"missingBucket\":false}},\"5df59fa2-37b5-4769-974a-523735722fe9\":{\"label\":\"Count of records\",\"dataType\":\"number\",\"operationType\":\"count\",\"isBucketed\":false,\"scale\":\"ratio\",\"sourceField\":\"Records\"}},\"columnOrder\":[\"3b51da1c-d1eb-44a2-9331-8f7a96a15fa4\",\"5df59fa2-37b5-4769-974a-523735722fe9\"],\"incompleteColumns\":{}}}}}}},\"hidePanelTitles\":true,\"enhancements\":{}}},{\"version\":\"7.15.0\",\"type\":\"lens\",\"gridData\":{\"x\":23,\"y\":11,\"w\":24,\"h\":15,\"i\":\"eb7fd91d-b9ed-4309-aa76-76e9b470d501\"},\"panelIndex\":\"eb7fd91d-b9ed-4309-aa76-76e9b470d501\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsPie\",\"type\":\"lens\",\"references\":[{\"id\":\"2b19d650-31e8-11ec-8fdb-bdb52ae291fb\",\"name\":\"indexpattern-datasource-current-indexpattern\",\"type\":\"index-pattern\"},{\"id\":\"2b19d650-31e8-11ec-8fdb-bdb52ae291fb\",\"name\":\"indexpattern-datasource-layer-b48706af-9fb8-46af-af8f-c110c3e87ab1\",\"type\":\"index-pattern\"}],\"state\":{\"visualization\":{\"shape\":\"donut\",\"layers\":[{\"layerId\":\"b48706af-9fb8-46af-af8f-c110c3e87ab1\",\"groups\":[\"21c619d6-210d-4d5f-84d2-7fae93f3a173\"],\"metric\":\"a25ff54a-2376-4e73-84fe-fed2ace9602a\",\"numberDisplay\":\"percent\",\"categoryDisplay\":\"default\",\"legendDisplay\":\"default\",\"nestedLegend\":false,\"layerType\":\"data\"}]},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"indexpattern\":{\"layers\":{\"b48706af-9fb8-46af-af8f-c110c3e87ab1\":{\"columns\":{\"21c619d6-210d-4d5f-84d2-7fae93f3a173\":{\"label\":\"Top values of File.Extension.keyword\",\"dataType\":\"string\",\"operationType\":\"terms\",\"scale\":\"ordinal\",\"sourceField\":\"File.Extension.keyword\",\"isBucketed\":true,\"params\":{\"size\":5,\"orderBy\":{\"type\":\"column\",\"columnId\":\"a25ff54a-2376-4e73-84fe-fed2ace9602a\"},\"orderDirection\":\"desc\",\"otherBucket\":true,\"missingBucket\":false}},\"a25ff54a-2376-4e73-84fe-fed2ace9602a\":{\"label\":\"Count of records\",\"dataType\":\"number\",\"operationType\":\"count\",\"isBucketed\":false,\"scale\":\"ratio\",\"sourceField\":\"Records\"}},\"columnOrder\":[\"21c619d6-210d-4d5f-84d2-7fae93f3a173\",\"a25ff54a-2376-4e73-84fe-fed2ace9602a\"],\"incompleteColumns\":{}}}}}}},\"enhancements\":{}}},{\"version\":\"7.15.0\",\"type\":\"visualization\",\"gridData\":{\"x\":0,\"y\":26,\"w\":47,\"h\":5,\"i\":\"57a901b1-ddfd-4cd9-a48b-3e8a959b47dd\"},\"panelIndex\":\"57a901b1-ddfd-4cd9-a48b-3e8a959b47dd\",\"embeddableConfig\":{\"savedVis\":{\"title\":\"\",\"description\":\"\",\"type\":\"markdown\",\"params\":{\"fontSize\":12,\"openLinksInNewTab\":false,\"markdown\":\"# Host machines and Users\"},\"uiState\":{},\"data\":{\"aggs\":[],\"searchSource\":{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}}},\"hidePanelTitles\":true,\"enhancements\":{}}},{\"version\":\"7.15.0\",\"type\":\"lens\",\"gridData\":{\"x\":0,\"y\":31,\"w\":23,\"h\":15,\"i\":\"9dfa65c4-70c7-4a5d-87ba-0744e6580883\"},\"panelIndex\":\"9dfa65c4-70c7-4a5d-87ba-0744e6580883\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsXY\",\"type\":\"lens\",\"references\":[{\"id\":\"2b19d650-31e8-11ec-8fdb-bdb52ae291fb\",\"name\":\"indexpattern-datasource-current-indexpattern\",\"type\":\"index-pattern\"},{\"id\":\"2b19d650-31e8-11ec-8fdb-bdb52ae291fb\",\"name\":\"indexpattern-datasource-layer-e675869d-d84a-444f-aea7-3ac793f44e71\",\"type\":\"index-pattern\"}],\"state\":{\"visualization\":{\"legend\":{\"isVisible\":true,\"position\":\"right\"},\"valueLabels\":\"hide\",\"fittingFunction\":\"None\",\"yLeftExtent\":{\"mode\":\"full\"},\"yRightExtent\":{\"mode\":\"full\"},\"axisTitlesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"tickLabelsVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"labelsOrientation\":{\"x\":0,\"yLeft\":0,\"yRight\":0},\"gridlinesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"preferredSeriesType\":\"bar_stacked\",\"layers\":[{\"layerId\":\"e675869d-d84a-444f-aea7-3ac793f44e71\",\"accessors\":[\"b8b026d1-7d7f-4043-8fe9-51d5fbc344aa\"],\"position\":\"top\",\"seriesType\":\"bar_stacked\",\"showGridlines\":false,\"layerType\":\"data\",\"xAccessor\":\"472d4f56-cfaa-425d-9ef5-31c686be8d25\"}]},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"indexpattern\":{\"layers\":{\"e675869d-d84a-444f-aea7-3ac793f44e71\":{\"columns\":{\"472d4f56-cfaa-425d-9ef5-31c686be8d25\":{\"label\":\"Top Hostnames\",\"dataType\":\"string\",\"operationType\":\"terms\",\"scale\":\"ordinal\",\"sourceField\":\"File.Hostname.keyword\",\"isBucketed\":true,\"params\":{\"size\":15,\"orderBy\":{\"type\":\"column\",\"columnId\":\"b8b026d1-7d7f-4043-8fe9-51d5fbc344aa\"},\"orderDirection\":\"desc\",\"otherBucket\":true,\"missingBucket\":false},\"customLabel\":true},\"b8b026d1-7d7f-4043-8fe9-51d5fbc344aa\":{\"label\":\"Count of records\",\"dataType\":\"number\",\"operationType\":\"count\",\"isBucketed\":false,\"scale\":\"ratio\",\"sourceField\":\"Records\"}},\"columnOrder\":[\"472d4f56-cfaa-425d-9ef5-31c686be8d25\",\"b8b026d1-7d7f-4043-8fe9-51d5fbc344aa\"],\"incompleteColumns\":{}}}}}}},\"hidePanelTitles\":true,\"enhancements\":{}}},{\"version\":\"7.15.0\",\"type\":\"lens\",\"gridData\":{\"x\":23,\"y\":31,\"w\":24,\"h\":15,\"i\":\"c10fbe95-24ae-4cbb-a5ef-a746b7d3ea3d\"},\"panelIndex\":\"c10fbe95-24ae-4cbb-a5ef-a746b7d3ea3d\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsXY\",\"type\":\"lens\",\"references\":[{\"id\":\"2b19d650-31e8-11ec-8fdb-bdb52ae291fb\",\"name\":\"indexpattern-datasource-current-indexpattern\",\"type\":\"index-pattern\"},{\"id\":\"2b19d650-31e8-11ec-8fdb-bdb52ae291fb\",\"name\":\"indexpattern-datasource-layer-b8e837c7-9f7c-4e2c-88f1-c01bdb40771e\",\"type\":\"index-pattern\"}],\"state\":{\"visualization\":{\"legend\":{\"isVisible\":true,\"position\":\"right\"},\"valueLabels\":\"hide\",\"fittingFunction\":\"None\",\"yLeftExtent\":{\"mode\":\"full\"},\"yRightExtent\":{\"mode\":\"full\"},\"axisTitlesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"tickLabelsVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"labelsOrientation\":{\"x\":0,\"yLeft\":0,\"yRight\":0},\"gridlinesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"preferredSeriesType\":\"bar_stacked\",\"layers\":[{\"layerId\":\"b8e837c7-9f7c-4e2c-88f1-c01bdb40771e\",\"accessors\":[\"be314cad-5fe4-431f-b1d4-31546697a0f2\"],\"position\":\"top\",\"seriesType\":\"bar_stacked\",\"showGridlines\":false,\"layerType\":\"data\",\"xAccessor\":\"d92e6d94-dc1d-4663-8195-371b380091c6\"}]},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"indexpattern\":{\"layers\":{\"b8e837c7-9f7c-4e2c-88f1-c01bdb40771e\":{\"columns\":{\"d92e6d94-dc1d-4663-8195-371b380091c6\":{\"label\":\"Top Usernames\",\"dataType\":\"string\",\"operationType\":\"terms\",\"scale\":\"ordinal\",\"sourceField\":\"File.Username.keyword\",\"isBucketed\":true,\"params\":{\"size\":15,\"orderBy\":{\"type\":\"column\",\"columnId\":\"be314cad-5fe4-431f-b1d4-31546697a0f2\"},\"orderDirection\":\"desc\",\"otherBucket\":true,\"missingBucket\":false},\"customLabel\":true},\"be314cad-5fe4-431f-b1d4-31546697a0f2\":{\"label\":\"Count of records\",\"dataType\":\"number\",\"operationType\":\"count\",\"isBucketed\":false,\"scale\":\"ratio\",\"sourceField\":\"Records\"}},\"columnOrder\":[\"d92e6d94-dc1d-4663-8195-371b380091c6\",\"be314cad-5fe4-431f-b1d4-31546697a0f2\"],\"incompleteColumns\":{}}}}}}},\"hidePanelTitles\":true,\"enhancements\":{}}},{\"version\":\"7.15.0\",\"type\":\"visualization\",\"gridData\":{\"x\":0,\"y\":46,\"w\":47,\"h\":5,\"i\":\"849e1fe5-e349-4ed2-ab01-6a8de0ee4345\"},\"panelIndex\":\"849e1fe5-e349-4ed2-ab01-6a8de0ee4345\",\"embeddableConfig\":{\"savedVis\":{\"title\":\"\",\"description\":\"\",\"type\":\"markdown\",\"params\":{\"fontSize\":12,\"openLinksInNewTab\":false,\"markdown\":\"# Users permissions\"},\"uiState\":{},\"data\":{\"aggs\":[],\"searchSource\":{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}}},\"hidePanelTitles\":true,\"enhancements\":{}}},{\"version\":\"7.15.0\",\"type\":\"lens\",\"gridData\":{\"x\":0,\"y\":51,\"w\":16,\"h\":9,\"i\":\"baf6b079-f9b3-41ce-8d41-74c7e2e57dcb\"},\"panelIndex\":\"baf6b079-f9b3-41ce-8d41-74c7e2e57dcb\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsMetric\",\"type\":\"lens\",\"references\":[{\"id\":\"2b19d650-31e8-11ec-8fdb-bdb52ae291fb\",\"name\":\"indexpattern-datasource-current-indexpattern\",\"type\":\"index-pattern\"},{\"id\":\"2b19d650-31e8-11ec-8fdb-bdb52ae291fb\",\"name\":\"indexpattern-datasource-layer-28a57aa2-5fb7-40b1-a90d-c342d3aca629\",\"type\":\"index-pattern\"},{\"id\":\"2b19d650-31e8-11ec-8fdb-bdb52ae291fb\",\"name\":\"filter-index-pattern-0\",\"type\":\"index-pattern\"}],\"state\":{\"visualization\":{\"layerId\":\"28a57aa2-5fb7-40b1-a90d-c342d3aca629\",\"accessor\":\"333ac877-f14a-4880-89d7-7caacab8ac23\",\"layerType\":\"data\"},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[{\"meta\":{\"alias\":null,\"negate\":false,\"disabled\":false,\"type\":\"phrase\",\"key\":\"File.Readable\",\"params\":{\"query\":true},\"indexRefName\":\"filter-index-pattern-0\"},\"query\":{\"match_phrase\":{\"File.Readable\":true}},\"$state\":{\"store\":\"appState\"}}],\"datasourceStates\":{\"indexpattern\":{\"layers\":{\"28a57aa2-5fb7-40b1-a90d-c342d3aca629\":{\"columns\":{\"333ac877-f14a-4880-89d7-7caacab8ac23\":{\"label\":\"Readble items\",\"dataType\":\"number\",\"operationType\":\"count\",\"isBucketed\":false,\"scale\":\"ratio\",\"sourceField\":\"Records\",\"customLabel\":true}},\"columnOrder\":[\"333ac877-f14a-4880-89d7-7caacab8ac23\"],\"incompleteColumns\":{}}}}}}},\"hidePanelTitles\":true,\"enhancements\":{}}},{\"version\":\"7.15.0\",\"type\":\"lens\",\"gridData\":{\"x\":16,\"y\":51,\"w\":16,\"h\":9,\"i\":\"06258b35-d7a5-4bab-9343-6bd76bad8e3a\"},\"panelIndex\":\"06258b35-d7a5-4bab-9343-6bd76bad8e3a\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsMetric\",\"type\":\"lens\",\"references\":[{\"id\":\"2b19d650-31e8-11ec-8fdb-bdb52ae291fb\",\"name\":\"indexpattern-datasource-current-indexpattern\",\"type\":\"index-pattern\"},{\"id\":\"2b19d650-31e8-11ec-8fdb-bdb52ae291fb\",\"name\":\"indexpattern-datasource-layer-81ff1937-0afe-4b3f-8ff2-61f44c8bac8e\",\"type\":\"index-pattern\"},{\"id\":\"2b19d650-31e8-11ec-8fdb-bdb52ae291fb\",\"name\":\"filter-index-pattern-0\",\"type\":\"index-pattern\"}],\"state\":{\"visualization\":{\"layerId\":\"81ff1937-0afe-4b3f-8ff2-61f44c8bac8e\",\"accessor\":\"9a1bc434-dde6-4d08-820b-5f157eb48dd2\",\"layerType\":\"data\"},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[{\"meta\":{\"alias\":null,\"negate\":false,\"disabled\":false,\"type\":\"phrase\",\"key\":\"File.Writeable\",\"params\":{\"query\":true},\"indexRefName\":\"filter-index-pattern-0\"},\"query\":{\"match_phrase\":{\"File.Writeable\":true}},\"$state\":{\"store\":\"appState\"}}],\"datasourceStates\":{\"indexpattern\":{\"layers\":{\"81ff1937-0afe-4b3f-8ff2-61f44c8bac8e\":{\"columns\":{\"9a1bc434-dde6-4d08-820b-5f157eb48dd2\":{\"label\":\"Writable\",\"dataType\":\"number\",\"operationType\":\"count\",\"isBucketed\":false,\"scale\":\"ratio\",\"sourceField\":\"Records\",\"customLabel\":true}},\"columnOrder\":[\"9a1bc434-dde6-4d08-820b-5f157eb48dd2\"],\"incompleteColumns\":{}}}}}}},\"hidePanelTitles\":true,\"enhancements\":{}}},{\"version\":\"7.15.0\",\"type\":\"lens\",\"gridData\":{\"x\":32,\"y\":51,\"w\":15,\"h\":9,\"i\":\"785db7c8-72b9-4966-9e5f-034dd0c7d6da\"},\"panelIndex\":\"785db7c8-72b9-4966-9e5f-034dd0c7d6da\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsMetric\",\"type\":\"lens\",\"references\":[{\"id\":\"2b19d650-31e8-11ec-8fdb-bdb52ae291fb\",\"name\":\"indexpattern-datasource-current-indexpattern\",\"type\":\"index-pattern\"},{\"id\":\"2b19d650-31e8-11ec-8fdb-bdb52ae291fb\",\"name\":\"indexpattern-datasource-layer-0d9d5403-acfb-4232-a9bc-1b3040716965\",\"type\":\"index-pattern\"},{\"id\":\"2b19d650-31e8-11ec-8fdb-bdb52ae291fb\",\"name\":\"filter-index-pattern-0\",\"type\":\"index-pattern\"}],\"state\":{\"visualization\":{\"layerId\":\"0d9d5403-acfb-4232-a9bc-1b3040716965\",\"accessor\":\"4f36f5fa-46b2-4b4a-a37d-47237e915df8\",\"layerType\":\"data\"},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[{\"meta\":{\"alias\":null,\"negate\":false,\"disabled\":false,\"type\":\"phrase\",\"key\":\"File.Deletable\",\"params\":{\"query\":true},\"indexRefName\":\"filter-index-pattern-0\"},\"query\":{\"match_phrase\":{\"File.Deletable\":true}},\"$state\":{\"store\":\"appState\"}}],\"datasourceStates\":{\"indexpattern\":{\"layers\":{\"0d9d5403-acfb-4232-a9bc-1b3040716965\":{\"columns\":{\"4f36f5fa-46b2-4b4a-a37d-47237e915df8\":{\"label\":\"Deletable\",\"dataType\":\"number\",\"operationType\":\"count\",\"isBucketed\":false,\"scale\":\"ratio\",\"sourceField\":\"Records\",\"customLabel\":true}},\"columnOrder\":[\"4f36f5fa-46b2-4b4a-a37d-47237e915df8\"],\"incompleteColumns\":{}}}}}}},\"hidePanelTitles\":true,\"enhancements\":{}}},{\"version\":\"7.15.0\",\"type\":\"lens\",\"gridData\":{\"x\":0,\"y\":60,\"w\":16,\"h\":9,\"i\":\"0fa7ac96-9ff5-4313-835b-843b8a82b999\"},\"panelIndex\":\"0fa7ac96-9ff5-4313-835b-843b8a82b999\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsMetric\",\"type\":\"lens\",\"references\":[{\"id\":\"2b19d650-31e8-11ec-8fdb-bdb52ae291fb\",\"name\":\"indexpattern-datasource-current-indexpattern\",\"type\":\"index-pattern\"},{\"id\":\"2b19d650-31e8-11ec-8fdb-bdb52ae291fb\",\"name\":\"indexpattern-datasource-layer-a9248dd4-d5d7-4759-8527-fb05528badd7\",\"type\":\"index-pattern\"},{\"id\":\"2b19d650-31e8-11ec-8fdb-bdb52ae291fb\",\"name\":\"filter-index-pattern-0\",\"type\":\"index-pattern\"}],\"state\":{\"visualization\":{\"layerId\":\"a9248dd4-d5d7-4759-8527-fb05528badd7\",\"accessor\":\"e58ba5ba-3006-4228-9b4c-6c1513c8891b\",\"layerType\":\"data\"},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[{\"meta\":{\"alias\":null,\"negate\":false,\"disabled\":false,\"type\":\"phrase\",\"key\":\"File.Readable\",\"params\":{\"query\":false},\"indexRefName\":\"filter-index-pattern-0\"},\"query\":{\"match_phrase\":{\"File.Readable\":false}},\"$state\":{\"store\":\"appState\"}}],\"datasourceStates\":{\"indexpattern\":{\"layers\":{\"a9248dd4-d5d7-4759-8527-fb05528badd7\":{\"columns\":{\"e58ba5ba-3006-4228-9b4c-6c1513c8891b\":{\"label\":\"Unreadable\",\"dataType\":\"number\",\"operationType\":\"count\",\"isBucketed\":false,\"scale\":\"ratio\",\"sourceField\":\"Records\",\"customLabel\":true}},\"columnOrder\":[\"e58ba5ba-3006-4228-9b4c-6c1513c8891b\"],\"incompleteColumns\":{}}}}}}},\"hidePanelTitles\":true,\"enhancements\":{}}},{\"version\":\"7.15.0\",\"type\":\"lens\",\"gridData\":{\"x\":16,\"y\":60,\"w\":16,\"h\":9,\"i\":\"1e0c23ee-dee7-4fed-b94c-f0eefc575dde\"},\"panelIndex\":\"1e0c23ee-dee7-4fed-b94c-f0eefc575dde\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsMetric\",\"type\":\"lens\",\"references\":[{\"id\":\"2b19d650-31e8-11ec-8fdb-bdb52ae291fb\",\"name\":\"indexpattern-datasource-current-indexpattern\",\"type\":\"index-pattern\"},{\"id\":\"2b19d650-31e8-11ec-8fdb-bdb52ae291fb\",\"name\":\"indexpattern-datasource-layer-b2b04818-385e-4e1e-9b38-b035499848e7\",\"type\":\"index-pattern\"},{\"id\":\"2b19d650-31e8-11ec-8fdb-bdb52ae291fb\",\"name\":\"filter-index-pattern-0\",\"type\":\"index-pattern\"}],\"state\":{\"visualization\":{\"layerId\":\"b2b04818-385e-4e1e-9b38-b035499848e7\",\"accessor\":\"80483893-e4f7-46a6-bc01-2de4fea4f318\",\"layerType\":\"data\"},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[{\"meta\":{\"alias\":null,\"negate\":false,\"disabled\":false,\"type\":\"phrase\",\"key\":\"File.Writeable\",\"params\":{\"query\":false},\"indexRefName\":\"filter-index-pattern-0\"},\"query\":{\"match_phrase\":{\"File.Writeable\":false}},\"$state\":{\"store\":\"appState\"}}],\"datasourceStates\":{\"indexpattern\":{\"layers\":{\"b2b04818-385e-4e1e-9b38-b035499848e7\":{\"columns\":{\"80483893-e4f7-46a6-bc01-2de4fea4f318\":{\"label\":\"Unwritable\",\"dataType\":\"number\",\"operationType\":\"count\",\"isBucketed\":false,\"scale\":\"ratio\",\"sourceField\":\"Records\",\"customLabel\":true}},\"columnOrder\":[\"80483893-e4f7-46a6-bc01-2de4fea4f318\"],\"incompleteColumns\":{}}}}}}},\"hidePanelTitles\":true,\"enhancements\":{}}},{\"version\":\"7.15.0\",\"type\":\"lens\",\"gridData\":{\"x\":32,\"y\":60,\"w\":15,\"h\":9,\"i\":\"6d8de2aa-e21b-4a20-bdb4-25757496eb18\"},\"panelIndex\":\"6d8de2aa-e21b-4a20-bdb4-25757496eb18\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsMetric\",\"type\":\"lens\",\"references\":[{\"id\":\"2b19d650-31e8-11ec-8fdb-bdb52ae291fb\",\"name\":\"indexpattern-datasource-current-indexpattern\",\"type\":\"index-pattern\"},{\"id\":\"2b19d650-31e8-11ec-8fdb-bdb52ae291fb\",\"name\":\"indexpattern-datasource-layer-d233b8c6-78d4-42b5-a9e8-de44aa8df1da\",\"type\":\"index-pattern\"},{\"id\":\"2b19d650-31e8-11ec-8fdb-bdb52ae291fb\",\"name\":\"filter-index-pattern-0\",\"type\":\"index-pattern\"}],\"state\":{\"visualization\":{\"layerId\":\"d233b8c6-78d4-42b5-a9e8-de44aa8df1da\",\"accessor\":\"54af1722-e4a4-4d69-9cde-5b32526fbffa\",\"layerType\":\"data\"},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[{\"meta\":{\"alias\":null,\"negate\":false,\"disabled\":false,\"type\":\"phrase\",\"key\":\"File.Deletable\",\"params\":{\"query\":false},\"indexRefName\":\"filter-index-pattern-0\"},\"query\":{\"match_phrase\":{\"File.Deletable\":false}},\"$state\":{\"store\":\"appState\"}}],\"datasourceStates\":{\"indexpattern\":{\"layers\":{\"d233b8c6-78d4-42b5-a9e8-de44aa8df1da\":{\"columns\":{\"54af1722-e4a4-4d69-9cde-5b32526fbffa\":{\"label\":\"Undeletable\",\"dataType\":\"number\",\"operationType\":\"count\",\"isBucketed\":false,\"scale\":\"ratio\",\"sourceField\":\"Records\",\"customLabel\":true}},\"columnOrder\":[\"54af1722-e4a4-4d69-9cde-5b32526fbffa\"],\"incompleteColumns\":{}}}}}}},\"hidePanelTitles\":true,\"enhancements\":{}}},{\"version\":\"7.15.0\",\"type\":\"visualization\",\"gridData\":{\"x\":0,\"y\":69,\"w\":47,\"h\":5,\"i\":\"db2a25a9-5bd9-4b28-9a28-5e7e40e63bda\"},\"panelIndex\":\"db2a25a9-5bd9-4b28-9a28-5e7e40e63bda\",\"embeddableConfig\":{\"savedVis\":{\"title\":\"\",\"description\":\"\",\"type\":\"markdown\",\"params\":{\"fontSize\":12,\"openLinksInNewTab\":false,\"markdown\":\"# Export Table\"},\"uiState\":{},\"data\":{\"aggs\":[],\"searchSource\":{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}}},\"hidePanelTitles\":true,\"enhancements\":{}}},{\"version\":\"7.15.0\",\"type\":\"lens\",\"gridData\":{\"x\":0,\"y\":74,\"w\":47,\"h\":16,\"i\":\"c1210942-a2cf-40c3-bc50-351d1832b96e\"},\"panelIndex\":\"c1210942-a2cf-40c3-bc50-351d1832b96e\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsDatatable\",\"type\":\"lens\",\"references\":[{\"type\":\"index-pattern\",\"id\":\"2b19d650-31e8-11ec-8fdb-bdb52ae291fb\",\"name\":\"indexpattern-datasource-current-indexpattern\"},{\"type\":\"index-pattern\",\"id\":\"2b19d650-31e8-11ec-8fdb-bdb52ae291fb\",\"name\":\"indexpattern-datasource-layer-4bae9f0e-72e2-45e2-8f6c-683fe98401ba\"}],\"state\":{\"visualization\":{\"columns\":[{\"isTransposed\":false,\"columnId\":\"7fed0fd1-c705-48d1-9a5e-886365d48797\"},{\"isTransposed\":false,\"columnId\":\"0a908651-5751-40be-a0cd-7a5695824669\"},{\"isTransposed\":false,\"columnId\":\"39f5a198-3465-4834-b287-8eca87be95ef\"},{\"isTransposed\":false,\"columnId\":\"7a48c5fa-6313-4ab5-a71f-ebf154d78b2d\"},{\"isTransposed\":false,\"columnId\":\"a384caf2-d2b0-488d-9fe8-222cac752fc9\"}],\"layerId\":\"4bae9f0e-72e2-45e2-8f6c-683fe98401ba\",\"layerType\":\"data\"},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"indexpattern\":{\"layers\":{\"4bae9f0e-72e2-45e2-8f6c-683fe98401ba\":{\"columns\":{\"7fed0fd1-c705-48d1-9a5e-886365d48797\":{\"label\":\"UNC Directory\",\"dataType\":\"string\",\"operationType\":\"terms\",\"scale\":\"ordinal\",\"sourceField\":\"File.UNCDirectory.keyword\",\"isBucketed\":true,\"params\":{\"size\":1000,\"orderBy\":{\"type\":\"column\",\"columnId\":\"a384caf2-d2b0-488d-9fe8-222cac752fc9\"},\"orderDirection\":\"desc\",\"otherBucket\":true,\"missingBucket\":false},\"customLabel\":true},\"0a908651-5751-40be-a0cd-7a5695824669\":{\"label\":\"Readable\",\"dataType\":\"boolean\",\"operationType\":\"terms\",\"scale\":\"ordinal\",\"sourceField\":\"File.Readable\",\"isBucketed\":true,\"params\":{\"size\":3,\"orderBy\":{\"type\":\"column\",\"columnId\":\"a384caf2-d2b0-488d-9fe8-222cac752fc9\"},\"orderDirection\":\"desc\",\"otherBucket\":true,\"missingBucket\":false},\"customLabel\":true},\"39f5a198-3465-4834-b287-8eca87be95ef\":{\"label\":\"Writeable\",\"dataType\":\"boolean\",\"operationType\":\"terms\",\"scale\":\"ordinal\",\"sourceField\":\"File.Writeable\",\"isBucketed\":true,\"params\":{\"size\":3,\"orderBy\":{\"type\":\"column\",\"columnId\":\"a384caf2-d2b0-488d-9fe8-222cac752fc9\"},\"orderDirection\":\"desc\",\"otherBucket\":true,\"missingBucket\":false},\"customLabel\":true},\"7a48c5fa-6313-4ab5-a71f-ebf154d78b2d\":{\"label\":\"Deletable\",\"dataType\":\"boolean\",\"operationType\":\"terms\",\"scale\":\"ordinal\",\"sourceField\":\"File.Deletable\",\"isBucketed\":true,\"params\":{\"size\":3,\"orderBy\":{\"type\":\"column\",\"columnId\":\"a384caf2-d2b0-488d-9fe8-222cac752fc9\"},\"orderDirection\":\"desc\",\"otherBucket\":true,\"missingBucket\":false},\"customLabel\":true},\"a384caf2-d2b0-488d-9fe8-222cac752fc9\":{\"label\":\"File count\",\"dataType\":\"number\",\"operationType\":\"count\",\"isBucketed\":false,\"scale\":\"ratio\",\"sourceField\":\"Records\",\"customLabel\":true}},\"columnOrder\":[\"7fed0fd1-c705-48d1-9a5e-886365d48797\",\"0a908651-5751-40be-a0cd-7a5695824669\",\"39f5a198-3465-4834-b287-8eca87be95ef\",\"7a48c5fa-6313-4ab5-a71f-ebf154d78b2d\",\"a384caf2-d2b0-488d-9fe8-222cac752fc9\"],\"incompleteColumns\":{}}}}}}},\"hidePanelTitles\":true,\"enhancements\":{}}}]","timeRestore":false,"title":"SMBeagle","version":1},"coreMigrationVersion":"7.15.0","id":"2e8f5130-31ec-11ec-8fdb-bdb52ae291fb","migrationVersion":{"dashboard":"7.15.0"},"references":[{"id":"2b19d650-31e8-11ec-8fdb-bdb52ae291fb","name":"2c218684-e98e-46ac-a255-b70736fa8ef6:indexpattern-datasource-current-indexpattern","type":"index-pattern"},{"id":"2b19d650-31e8-11ec-8fdb-bdb52ae291fb","name":"2c218684-e98e-46ac-a255-b70736fa8ef6:indexpattern-datasource-layer-7ff74256-fcbd-4520-bebf-7f136af63cf4","type":"index-pattern"},{"id":"2b19d650-31e8-11ec-8fdb-bdb52ae291fb","name":"3c187785-39ca-4649-beff-e952f84c73c6:indexpattern-datasource-current-indexpattern","type":"index-pattern"},{"id":"2b19d650-31e8-11ec-8fdb-bdb52ae291fb","name":"3c187785-39ca-4649-beff-e952f84c73c6:indexpattern-datasource-layer-342c5503-0e6b-413f-a6e4-395f4c5738dc","type":"index-pattern"},{"id":"2b19d650-31e8-11ec-8fdb-bdb52ae291fb","name":"a6401f2f-b3cd-4f43-83b9-d0d559c2bfe7:indexpattern-datasource-current-indexpattern","type":"index-pattern"},{"id":"2b19d650-31e8-11ec-8fdb-bdb52ae291fb","name":"a6401f2f-b3cd-4f43-83b9-d0d559c2bfe7:indexpattern-datasource-layer-84c522d2-d32a-48f1-9230-cbcdd086bb65","type":"index-pattern"},{"id":"2b19d650-31e8-11ec-8fdb-bdb52ae291fb","name":"da33a7b7-05a1-4186-b0f6-d343e49befe1:indexpattern-datasource-current-indexpattern","type":"index-pattern"},{"id":"2b19d650-31e8-11ec-8fdb-bdb52ae291fb","name":"da33a7b7-05a1-4186-b0f6-d343e49befe1:indexpattern-datasource-layer-42c75835-7aff-4c8c-bbb0-0be4bf175acd","type":"index-pattern"},{"id":"2b19d650-31e8-11ec-8fdb-bdb52ae291fb","name":"50167deb-ac78-4a95-9762-7215948d1c50:indexpattern-datasource-current-indexpattern","type":"index-pattern"},{"id":"2b19d650-31e8-11ec-8fdb-bdb52ae291fb","name":"50167deb-ac78-4a95-9762-7215948d1c50:indexpattern-datasource-layer-76982cc4-fc81-4614-bee1-283454f055ff","type":"index-pattern"},{"id":"2b19d650-31e8-11ec-8fdb-bdb52ae291fb","name":"eb7fd91d-b9ed-4309-aa76-76e9b470d501:indexpattern-datasource-current-indexpattern","type":"index-pattern"},{"id":"2b19d650-31e8-11ec-8fdb-bdb52ae291fb","name":"eb7fd91d-b9ed-4309-aa76-76e9b470d501:indexpattern-datasource-layer-b48706af-9fb8-46af-af8f-c110c3e87ab1","type":"index-pattern"},{"id":"2b19d650-31e8-11ec-8fdb-bdb52ae291fb","name":"9dfa65c4-70c7-4a5d-87ba-0744e6580883:indexpattern-datasource-current-indexpattern","type":"index-pattern"},{"id":"2b19d650-31e8-11ec-8fdb-bdb52ae291fb","name":"9dfa65c4-70c7-4a5d-87ba-0744e6580883:indexpattern-datasource-layer-e675869d-d84a-444f-aea7-3ac793f44e71","type":"index-pattern"},{"id":"2b19d650-31e8-11ec-8fdb-bdb52ae291fb","name":"c10fbe95-24ae-4cbb-a5ef-a746b7d3ea3d:indexpattern-datasource-current-indexpattern","type":"index-pattern"},{"id":"2b19d650-31e8-11ec-8fdb-bdb52ae291fb","name":"c10fbe95-24ae-4cbb-a5ef-a746b7d3ea3d:indexpattern-datasource-layer-b8e837c7-9f7c-4e2c-88f1-c01bdb40771e","type":"index-pattern"},{"id":"2b19d650-31e8-11ec-8fdb-bdb52ae291fb","name":"baf6b079-f9b3-41ce-8d41-74c7e2e57dcb:indexpattern-datasource-current-indexpattern","type":"index-pattern"},{"id":"2b19d650-31e8-11ec-8fdb-bdb52ae291fb","name":"baf6b079-f9b3-41ce-8d41-74c7e2e57dcb:indexpattern-datasource-layer-28a57aa2-5fb7-40b1-a90d-c342d3aca629","type":"index-pattern"},{"id":"2b19d650-31e8-11ec-8fdb-bdb52ae291fb","name":"baf6b079-f9b3-41ce-8d41-74c7e2e57dcb:filter-index-pattern-0","type":"index-pattern"},{"id":"2b19d650-31e8-11ec-8fdb-bdb52ae291fb","name":"06258b35-d7a5-4bab-9343-6bd76bad8e3a:indexpattern-datasource-current-indexpattern","type":"index-pattern"},{"id":"2b19d650-31e8-11ec-8fdb-bdb52ae291fb","name":"06258b35-d7a5-4bab-9343-6bd76bad8e3a:indexpattern-datasource-layer-81ff1937-0afe-4b3f-8ff2-61f44c8bac8e","type":"index-pattern"},{"id":"2b19d650-31e8-11ec-8fdb-bdb52ae291fb","name":"06258b35-d7a5-4bab-9343-6bd76bad8e3a:filter-index-pattern-0","type":"index-pattern"},{"id":"2b19d650-31e8-11ec-8fdb-bdb52ae291fb","name":"785db7c8-72b9-4966-9e5f-034dd0c7d6da:indexpattern-datasource-current-indexpattern","type":"index-pattern"},{"id":"2b19d650-31e8-11ec-8fdb-bdb52ae291fb","name":"785db7c8-72b9-4966-9e5f-034dd0c7d6da:indexpattern-datasource-layer-0d9d5403-acfb-4232-a9bc-1b3040716965","type":"index-pattern"},{"id":"2b19d650-31e8-11ec-8fdb-bdb52ae291fb","name":"785db7c8-72b9-4966-9e5f-034dd0c7d6da:filter-index-pattern-0","type":"index-pattern"},{"id":"2b19d650-31e8-11ec-8fdb-bdb52ae291fb","name":"0fa7ac96-9ff5-4313-835b-843b8a82b999:indexpattern-datasource-current-indexpattern","type":"index-pattern"},{"id":"2b19d650-31e8-11ec-8fdb-bdb52ae291fb","name":"0fa7ac96-9ff5-4313-835b-843b8a82b999:indexpattern-datasource-layer-a9248dd4-d5d7-4759-8527-fb05528badd7","type":"index-pattern"},{"id":"2b19d650-31e8-11ec-8fdb-bdb52ae291fb","name":"0fa7ac96-9ff5-4313-835b-843b8a82b999:filter-index-pattern-0","type":"index-pattern"},{"id":"2b19d650-31e8-11ec-8fdb-bdb52ae291fb","name":"1e0c23ee-dee7-4fed-b94c-f0eefc575dde:indexpattern-datasource-current-indexpattern","type":"index-pattern"},{"id":"2b19d650-31e8-11ec-8fdb-bdb52ae291fb","name":"1e0c23ee-dee7-4fed-b94c-f0eefc575dde:indexpattern-datasource-layer-b2b04818-385e-4e1e-9b38-b035499848e7","type":"index-pattern"},{"id":"2b19d650-31e8-11ec-8fdb-bdb52ae291fb","name":"1e0c23ee-dee7-4fed-b94c-f0eefc575dde:filter-index-pattern-0","type":"index-pattern"},{"id":"2b19d650-31e8-11ec-8fdb-bdb52ae291fb","name":"6d8de2aa-e21b-4a20-bdb4-25757496eb18:indexpattern-datasource-current-indexpattern","type":"index-pattern"},{"id":"2b19d650-31e8-11ec-8fdb-bdb52ae291fb","name":"6d8de2aa-e21b-4a20-bdb4-25757496eb18:indexpattern-datasource-layer-d233b8c6-78d4-42b5-a9e8-de44aa8df1da","type":"index-pattern"},{"id":"2b19d650-31e8-11ec-8fdb-bdb52ae291fb","name":"6d8de2aa-e21b-4a20-bdb4-25757496eb18:filter-index-pattern-0","type":"index-pattern"},{"id":"2b19d650-31e8-11ec-8fdb-bdb52ae291fb","name":"c1210942-a2cf-40c3-bc50-351d1832b96e:indexpattern-datasource-current-indexpattern","type":"index-pattern"},{"id":"2b19d650-31e8-11ec-8fdb-bdb52ae291fb","name":"c1210942-a2cf-40c3-bc50-351d1832b96e:indexpattern-datasource-layer-4bae9f0e-72e2-45e2-8f6c-683fe98401ba","type":"index-pattern"}],"sort":[1658044201202,84],"type":"dashboard","updated_at":"2022-07-17T07:50:01.202Z","version":"WzEwMywyXQ=="} 3 | {"attributes":{"buildNum":44040,"defaultIndex":"2b19d650-31e8-11ec-8fdb-bdb52ae291fb"},"coreMigrationVersion":"7.15.0","id":"7.15.0","migrationVersion":{"config":"7.13.0"},"references":[],"sort":[1658043624893,8],"type":"config","updated_at":"2022-07-17T07:40:24.893Z","version":"WzIyLDFd"} 4 | {"excludedObjects":[],"excludedObjectsCount":0,"exportedCount":3,"missingRefCount":0,"missingReferences":[]} -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 |  Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /NetworkDiscovery/Network.cs: -------------------------------------------------------------------------------- 1 | using SMBeagle.Enums; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using System.Net; 6 | 7 | namespace SMBeagle.NetworkDiscovery 8 | { 9 | class Network 10 | { 11 | static Dictionary _PrivateNetworks = new Dictionary 12 | { 13 | { "IPV4_RFC1918_1", IPNetwork2.Parse("10.0.0.0/8") }, 14 | { "IPV4_RFC1918_2", IPNetwork2.Parse("172.16.0.0/12") }, 15 | { "IPV4_RFC1918_3", IPNetwork2.Parse("192.168.0.0/16") }, 16 | { "IPV4_CGRADE_NAT", IPNetwork2.Parse("100.64.0.0/10") }, //Might get rid of this one as not really used? 17 | { "IPV4_LINK-LOCAL", IPNetwork2.Parse("169.254.0.0/16") }, 18 | { "IPV6_RFC4193", IPNetwork2.Parse("fd00::/8") }, 19 | { "IPV6_LINK-LOCAL", IPNetwork2.Parse("fe80::/10") }, 20 | }; 21 | private IPNetwork2 _Net { get; set; } 22 | public bool IsPrivate { get { return _IsPrivate; } } 23 | private bool _IsPrivate { get; set; } = false; 24 | public int IPVersion { get 25 | { 26 | if (Address.Contains(":")) 27 | return 6; 28 | else 29 | return 4; 30 | } 31 | } 32 | public string Address 33 | { 34 | get 35 | { 36 | return _Net.Network.ToString(); 37 | } 38 | } 39 | public string Mask 40 | { 41 | get 42 | { 43 | return _Net.Netmask.ToString(); 44 | } 45 | } 46 | public string Cidr 47 | { 48 | get 49 | { 50 | return _Net.Cidr.ToString(); 51 | } 52 | } 53 | 54 | public string Value 55 | { 56 | get 57 | { 58 | return _Net.Value; 59 | } 60 | } 61 | 62 | public override string ToString() 63 | { 64 | return Value; 65 | } 66 | 67 | public List AddressList { 68 | get 69 | { 70 | List addresses = new List(); 71 | foreach (IPAddress ip in _Net.ListIPAddress()) 72 | { 73 | addresses.Add(ip.ToString()); 74 | } 75 | return addresses; 76 | 77 | } 78 | } 79 | 80 | public NetworkDiscoverySourceEnum Source { get; } 81 | public Network(string cidr, NetworkDiscoverySourceEnum source) 82 | { 83 | //TODO: Validate its a cidr? 84 | Source = source; 85 | _Net = IPNetwork2.Parse(cidr); 86 | foreach (string name in _PrivateNetworks.Keys) 87 | { 88 | if (_PrivateNetworks[name].Contains(_Net)) 89 | { 90 | _IsPrivate = true; 91 | } 92 | } 93 | } 94 | public static bool IsPrivateAddress(string address) 95 | { 96 | foreach (string name in _PrivateNetworks.Keys) 97 | { 98 | IPAddress ip = IPAddress.Parse(address); 99 | if (_PrivateNetworks[name].Contains(ip)) 100 | { 101 | return true; 102 | } 103 | } 104 | return false; 105 | } 106 | 107 | public bool ContainsNetwork(Network network) 108 | { 109 | if (IPVersion != network.IPVersion) 110 | return false; 111 | IPNetwork2 childNet = IPNetwork2.Parse(network.Value); 112 | return _Net.Contains(childNet); 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /NetworkDiscovery/NetworkFinder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Net.NetworkInformation; 6 | using SMBeagle.Enums; 7 | 8 | namespace SMBeagle.NetworkDiscovery 9 | { 10 | class NetworkFinder 11 | { 12 | public List PrivateNetworks { 13 | get 14 | { 15 | return _Networks.Where(item => item.IsPrivate).ToList(); 16 | } 17 | } 18 | 19 | public List PublicNetworks 20 | { 21 | get 22 | { 23 | return _Networks.Where(item => !item.IsPrivate).ToList(); 24 | } 25 | } 26 | 27 | List _Networks = new List(); 28 | List _Addresses = new List(); 29 | List _LocalAddresses = new List(); 30 | public List Networks { get { return _Networks; } } 31 | public List PrivateAddresses 32 | { 33 | get 34 | { 35 | return _Addresses.Where(item => Network.IsPrivateAddress(item) == true).ToList(); 36 | } 37 | } 38 | public List PublicAddresses 39 | { 40 | get 41 | { 42 | return _Addresses.Where(item => Network.IsPrivateAddress(item) == false).ToList(); 43 | } 44 | } 45 | public List Addresses { get { return _Addresses; } } 46 | public List LocalAddresses { get { return _LocalAddresses; } } 47 | public NetworkFinder() 48 | { 49 | } 50 | 51 | public void AddAddress(string address) 52 | { 53 | // Remove ScopeID on IPv6 54 | if (address.Contains("%")) 55 | address = address.Substring(0, address.IndexOf("%")); 56 | // Dont store loopback 57 | if (address.Length > 3 && address.Substring(0, 4) == "127.") 58 | return; 59 | if (address == "::1") 60 | return; 61 | // Dont store if it already exists 62 | if (_Addresses.Contains(address)) 63 | return; 64 | // Store 65 | _Addresses.Add(address); 66 | } 67 | public void AddNetwork(string network, NetworkDiscoverySourceEnum source) 68 | { 69 | Network net = new Network(network, source); 70 | AddNetwork(net); 71 | } 72 | public void AddNetwork(Network network) 73 | { 74 | if (network.Value == "::/64" || network.Address == "127.0.0.0") 75 | return; // return without storing if this network already exists 76 | if (_Networks.Any(item => item.Value == network.Value)) 77 | return; // return without storing if network is child of another already tracked 78 | if (_Networks.Any(item => item.ContainsNetwork(network))) 79 | return; // remove childnet if this network fully contains it 80 | // todo: are there edge cases where we want to keep child nets? 81 | List iter = new List(_Networks); 82 | foreach ( Network net in iter) 83 | { 84 | if (network.ContainsNetwork(net)) 85 | _Networks.Remove(net); 86 | } 87 | _Networks.Add(network); 88 | } 89 | public void DiscoverNetworks() 90 | { 91 | DiscoverNetworksViaSockets(); 92 | DiscoverNetworksViaClientConfiguration(); 93 | } 94 | 95 | public void DiscoverNetworksViaSockets() 96 | { 97 | List addresses = DiscoverAddressesViaSockets(); 98 | List networks = new List(); 99 | foreach (string address in addresses) 100 | { 101 | // Expect a IPv4 net to be /24 and a IPv6 to be /64 102 | Network net = new Network(ConvertAddressToNetwork(address), NetworkDiscoverySourceEnum.NETSTAT); 103 | AddNetwork(net); 104 | } 105 | } 106 | 107 | private string ConvertAddressToNetwork(string address, string subnetmask = null, int cidr = 0) 108 | { 109 | // Remove ScopeID on IPv6 110 | if (address.Contains("%")) 111 | address = address.Substring(0,address.IndexOf("%")); 112 | if (subnetmask != null && subnetmask != "0.0.0.0") 113 | { 114 | cidr = IPNetwork2.ToCidr(IPAddress.Parse(subnetmask)); 115 | } 116 | // Set cidr 117 | if (cidr == 0) 118 | cidr = address.Contains(":") ? 64 : 24; 119 | return string.Format("{0}/{1}", address, cidr); 120 | } 121 | 122 | public List DiscoverAddressesViaSockets() 123 | { 124 | IPGlobalProperties properties = IPGlobalProperties.GetIPGlobalProperties(); 125 | TcpConnectionInformation[] connections = properties.GetActiveTcpConnections(); 126 | var addresses = new List(); 127 | foreach (var connection in connections) 128 | { 129 | AddAddress(connection.RemoteEndPoint.Address.ToString()); 130 | addresses.Add(connection.RemoteEndPoint.Address.ToString()); 131 | addresses.Add(connection.LocalEndPoint.Address.ToString()); 132 | } 133 | return new HashSet(addresses).ToList(); 134 | } 135 | 136 | public List DiscoverNetworksViaClientConfiguration(bool store=true) 137 | { 138 | List localAddresses = new(); 139 | foreach (NetworkInterface iface in NetworkInterface.GetAllNetworkInterfaces()) 140 | { 141 | List addresses = iface.GetIPProperties().UnicastAddresses.ToList(); 142 | foreach (UnicastIPAddressInformation address in addresses) 143 | { 144 | localAddresses.Add(address.Address.ToString()); 145 | if (store) 146 | { 147 | AddLocalAddress(address.Address.ToString()); 148 | // Convert to network and attempt to store 149 | string net = ConvertAddressToNetwork(address.Address.ToString(), address.IPv4Mask.ToString()); 150 | AddNetwork(net, NetworkDiscoverySourceEnum.LOCAL); 151 | } 152 | } 153 | } 154 | return localAddresses; 155 | } 156 | 157 | public void AddLocalAddress(string address) 158 | { 159 | if (address.Contains("%")) 160 | address = address.Substring(0, address.IndexOf("%")); 161 | if (_LocalAddresses.Contains(address)) 162 | return; 163 | _LocalAddresses.Add(address); 164 | } 165 | 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /Output/IOutputPayload.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SMBeagle.Output 8 | { 9 | public interface IOutputPayload 10 | { 11 | public string Username { get; set; } 12 | public string Hostname { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Output/OutputHelper.cs: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | using Serilog.Events; 3 | using Serilog.Formatting; 4 | using Serilog.Formatting.Compact; 5 | using Serilog.Formatting.Json; 6 | using Serilog.Sinks.Elasticsearch; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.IO; 10 | using System.Linq; 11 | using System.Net; 12 | using System.Net.NetworkInformation; 13 | using System.Reflection; 14 | using System.Security.Principal; 15 | 16 | namespace SMBeagle.Output 17 | { 18 | public static class OutputHelper 19 | { 20 | #region Private properties 21 | static string LOGO = @$" 22 | ____ __ _____ _ __ 23 | / __ \__ ______ / /__/ ___/___ _______ _______(_) /___ __ 24 | / /_/ / / / / __ \/ //_/\__ \/ _ \/ ___/ / / / ___/ / __/ / / / 25 | / ____/ /_/ / / / / ,< ___/ / __/ /__/ /_/ / / / / /_/ /_/ / 26 | /_/ \__,_/_/ /_/_/|_|/____/\___/\___/\__,_/_/ /_/\__/\__, / 27 | PRESENTS /____/ 28 | 29 | -- SMBeagle v{Assembly.GetEntryAssembly().GetName().Version.Major}.{Assembly.GetEntryAssembly().GetName().Version.Minor}.{Assembly.GetEntryAssembly().GetName().Version.Build} -- 30 | 31 | 32 | "; 33 | 34 | static ILogger ElasticsearchLogger { get; set; } = null; 35 | 36 | static ILogger CsvLogger { get; set; } = null; 37 | 38 | static readonly CompactJsonFormatter _jsonFormatter = new(new JsonValueFormatter(null)); 39 | 40 | static string Hostname { get; set; } 41 | 42 | static string Username { get; set; } 43 | 44 | static void SetUsernameAndHostname(string username) 45 | { 46 | Username = string.IsNullOrEmpty(username) ? Environment.UserName : username; 47 | Hostname = GetHostname(); 48 | } 49 | 50 | #endregion 51 | 52 | #region Public methods 53 | 54 | public static void EnableElasticsearchLogging(string nodeUris, string username = "") 55 | { 56 | SetUsernameAndHostname(username); 57 | // Need to do Index template to match the engine 58 | ElasticsearchLogger = new LoggerConfiguration() 59 | .WriteTo.Elasticsearch( 60 | customFormatter: _jsonFormatter, 61 | nodeUris: nodeUris, 62 | autoRegisterTemplate: true, 63 | autoRegisterTemplateVersion: AutoRegisterTemplateVersion.ESv7, 64 | indexFormat: "SMBeagle-{0:yyyy.MM.dd}" 65 | ) 66 | .CreateLogger(); 67 | } 68 | 69 | public static void EnableCSVLogging(string path, string username="") 70 | { 71 | SetUsernameAndHostname(username); 72 | try 73 | { 74 | System.IO.File.WriteAllText(path, string.Empty); 75 | } 76 | catch 77 | { 78 | Console.WriteLine("ERROR: Could not create CSV file"); 79 | Environment.Exit(1); 80 | } 81 | CsvLogger = new LoggerConfiguration() 82 | .WriteTo.File(new CSVFormatter(), path) 83 | .CreateLogger(); 84 | } 85 | 86 | public static void CloseAndFlush() 87 | { 88 | if (ElasticsearchLogger != null) 89 | { 90 | Log.Logger = ElasticsearchLogger; 91 | Log.CloseAndFlush(); 92 | } 93 | 94 | if (CsvLogger != null) 95 | { 96 | Log.Logger = CsvLogger; 97 | Log.CloseAndFlush(); 98 | } 99 | } 100 | 101 | public static void AddPayload(IOutputPayload payload, Enums.OutputtersEnum author) 102 | { 103 | payload.Hostname = Hostname; 104 | payload.Username = Username; 105 | LogOut("{hostname}:{username}:{@" + author + "}", payload); 106 | } 107 | 108 | public static void ConsoleWriteLogo() 109 | { 110 | Console.Write(LOGO); 111 | } 112 | 113 | public static void WriteLine(string line, int indent = 0, bool newline = true) 114 | { 115 | string pad = new(' ', indent * 2); 116 | if (newline) 117 | Console.WriteLine(pad + line); 118 | else 119 | Console.Write(pad + line); 120 | } 121 | 122 | #endregion 123 | 124 | #region Private methods 125 | 126 | static string GetHostname() 127 | { 128 | string 129 | domainName = IPGlobalProperties.GetIPGlobalProperties().DomainName, 130 | hostname = Dns.GetHostName(); 131 | 132 | if (domainName == String.Empty) 133 | domainName = "WORKGROUP"; 134 | 135 | if (domainName == "(none)") 136 | domainName = "STANDALONE"; 137 | 138 | return $"{hostname}.{domainName}"; 139 | } 140 | 141 | static void LogOut(string msg, IOutputPayload payload) 142 | { 143 | if (ElasticsearchLogger != null) 144 | ElasticsearchLogger.Information(msg, Hostname, Username, payload); 145 | 146 | if (CsvLogger != null) 147 | CsvLogger.Information(msg, Hostname, Username, payload); 148 | } 149 | 150 | #endregion 151 | } 152 | 153 | public class CSVFormatter : ITextFormatter 154 | { 155 | #region Constants 156 | 157 | const char CSV_SEPERATOR = ','; 158 | 159 | #endregion 160 | 161 | #region Static 162 | 163 | private static bool _headersWritten = false; 164 | 165 | #endregion 166 | 167 | #region ITextFormatter 168 | 169 | public void Format(LogEvent logEvent, TextWriter output) 170 | { 171 | try 172 | { 173 | var properties = ((Serilog.Events.StructureValue)logEvent.Properties["File"]).Properties; 174 | if (!_headersWritten) 175 | { 176 | for(int i=0; i with.HelpWriter = null); 23 | var parserResult = parser.ParseArguments(args); 24 | parserResult 25 | .WithParsed(Run) 26 | .WithNotParsed(errs => OutputHelp(parserResult, errs)); 27 | } 28 | 29 | static void Run(Options opts) 30 | { 31 | 32 | if (!opts.Quiet) 33 | OutputHelper.ConsoleWriteLogo(); 34 | else 35 | Console.WriteLine("SMBeagle by PunkSecurity [punksecurity.co.uk]"); 36 | 37 | if (! RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) 38 | { 39 | // TODO: should we have an enum for exit codes? 40 | if (opts.Username == null|| opts.Password == null) 41 | { 42 | OutputHelper.WriteLine("ERROR: Username and Password required on none Windows platforms"); 43 | Environment.Exit(1); 44 | } 45 | } 46 | 47 | if (opts.Username == null ^ opts.Password == null) 48 | { 49 | OutputHelper.WriteLine("ERROR: We need a username and password, not just one"); 50 | Environment.Exit(1); 51 | } 52 | bool crossPlatform = false; 53 | if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || opts.Username != null ) 54 | { 55 | crossPlatform = true; 56 | // The library we use hangs when scanning ourselves 57 | if (opts.ScanLocalShares) 58 | { 59 | OutputHelper.WriteLine("ERROR: We cannot scan local shares when running on Linux or with commandline credentials"); 60 | Environment.Exit(1); 61 | } 62 | } 63 | 64 | String username = ""; 65 | if (opts.Username != null) 66 | username = opts.Username; 67 | if (opts.Domain != "") 68 | username = $"{opts.Domain}\\{username}"; 69 | 70 | if (opts.ElasticsearchHost != null && opts.ElasticsearchPort != null) 71 | OutputHelper.EnableElasticsearchLogging($"http://{opts.ElasticsearchHost}:{opts.ElasticsearchPort}/", username); 72 | 73 | if (opts.CsvFile != null) 74 | OutputHelper.EnableCSVLogging(opts.CsvFile, username); 75 | 76 | NetworkFinder 77 | nf = new(); 78 | 79 | // Discover networks automagically 80 | if (!opts.DisableNetworkDiscovery) 81 | { 82 | OutputHelper.WriteLine("1. Performing network discovery..."); 83 | nf.DiscoverNetworks(); 84 | 85 | OutputHelper.WriteLine($"discovered {nf.PrivateNetworks.Count} private networks and {nf.PrivateAddresses.Count} private addresses", 1); 86 | 87 | if (!opts.Quiet) 88 | { 89 | OutputHelper.WriteLine("private networks:", 2); 90 | foreach (Network pn in nf.PrivateNetworks) 91 | OutputHelper.WriteLine(pn.ToString(), 3); 92 | OutputHelper.WriteLine("private addresses:", 2); 93 | foreach (string pa in nf.PrivateAddresses) 94 | OutputHelper.WriteLine(pa.ToString(), 3); 95 | } 96 | 97 | if (opts.Verbose) 98 | { 99 | OutputHelper.WriteLine($"discovered but will ignore the following {nf.PublicAddresses.Count} public addresses:", 1); 100 | foreach (string pa in nf.PublicAddresses) 101 | OutputHelper.WriteLine(pa, 2); 102 | OutputHelper.WriteLine($"discovered but will ignore the following {nf.PublicNetworks.Count} public networks:", 1); 103 | foreach (Network pn in nf.PublicNetworks) 104 | OutputHelper.WriteLine(pn.ToString(), 2); 105 | } 106 | } 107 | 108 | else 109 | { 110 | OutputHelper.WriteLine("1. Skipping network discovery due to -D switch..."); 111 | } 112 | 113 | // build list of provided exclusions 114 | List filteredAddresses = new(); 115 | List networks = new(); 116 | 117 | if (!opts.DisableNetworkDiscovery) 118 | { 119 | OutputHelper.WriteLine("2. Filtering discovered networks and addresses..."); 120 | 121 | // build list of discovered and provided networks 122 | Int16 123 | maxNetworkSizeForScanning = Int16.Parse(opts.MaxNetworkSizeForScanning); 124 | 125 | networks = nf.PrivateNetworks 126 | .Where(item => item.IPVersion == 4) // We cannot scan ipv6 networks, they are HUGE, but we do scan the ipv6 hosts 127 | .Where(item => Int16.Parse(item.Cidr) >= maxNetworkSizeForScanning) 128 | .Where(item => !opts.ExcludedNetworks.Contains(item.ToString())) 129 | .ToList(); 130 | 131 | OutputHelper.WriteLine($"filtered and have {networks.Count} private networks to scan and {filteredAddresses.Count} private addresses to exclude", 1); 132 | 133 | if (!opts.Quiet) 134 | { 135 | if (networks.Count > 0) 136 | { 137 | OutputHelper.WriteLine("private networks to scan:", 2); 138 | foreach (Network pn in networks) 139 | OutputHelper.WriteLine(pn.ToString(), 3); 140 | } 141 | 142 | 143 | if (filteredAddresses.Count > 0) 144 | { 145 | OutputHelper.WriteLine("private addresses to exclude:", 2); 146 | foreach (string pa in filteredAddresses) 147 | OutputHelper.WriteLine(pa, 3); 148 | } 149 | } 150 | } 151 | else 152 | { 153 | OutputHelper.WriteLine("2. Skipping filtering as network discovery disabled..."); 154 | } 155 | 156 | if (! opts.ScanLocalShares) 157 | { 158 | filteredAddresses.AddRange(nf.DiscoverNetworksViaClientConfiguration(store:false)); 159 | } 160 | filteredAddresses.AddRange(opts.ExcludedHosts.ToList()); 161 | 162 | List addresses = new(); 163 | 164 | if (opts.Networks.Any() || opts.Hosts.Any()) 165 | { 166 | OutputHelper.WriteLine("3. Processing manual networks and addresses..."); 167 | foreach (string network in opts.Networks) 168 | { 169 | networks.Add( 170 | new Network(network, Enums.NetworkDiscoverySourceEnum.ARGS) 171 | ); 172 | OutputHelper.WriteLine($"added network '{network}'", 1); 173 | 174 | } 175 | 176 | foreach (string address in opts.Hosts) 177 | { 178 | addresses.Add(address); 179 | OutputHelper.WriteLine($"added host '{address}'", 1); 180 | 181 | } 182 | 183 | } 184 | else 185 | { 186 | OutputHelper.WriteLine("3. No manual networks or addresses provided, skipping..."); 187 | } 188 | 189 | if (addresses.Count == 0 && networks.Count == 0) 190 | { 191 | OutputHelper.WriteLine("After filtering - there are no networks or hosts to scan..."); 192 | Environment.Exit(0); 193 | } 194 | 195 | OutputHelper.WriteLine("4. Probing hosts and scanning networks for SMB port 445..."); 196 | 197 | //TODO: add none quiet output to show what we are scanning at this point - nets, hosts and exclusiosn 198 | 199 | // Begin the scan for up hosts 200 | HostFinder 201 | hf = new(addresses, networks, filteredAddresses); 202 | 203 | OutputHelper.WriteLine($"scanning is complete and we have {hf.ReachableHosts.Count} hosts with reachable SMB services", 1); 204 | 205 | if (hf.ReachableHosts.Count == 0) 206 | { 207 | OutputHelper.WriteLine("There are no hosts with accessible SMB services..."); 208 | Environment.Exit(0); 209 | } 210 | 211 | if (opts.Verbose) 212 | { 213 | OutputHelper.WriteLine($"reachable hosts:", 2); 214 | foreach (Host h in hf.ReachableHosts) 215 | OutputHelper.WriteLine(h.Address, 3); 216 | } 217 | 218 | OutputHelper.WriteLine("5. Probing SMB services for accessible shares..."); 219 | 220 | if (crossPlatform) 221 | { 222 | foreach (Host host in hf.ReachableHosts) 223 | { 224 | Thread t = new(() => CrossPlatformShareFinder.DiscoverDeviceShares(host, opts.Domain, opts.Username, opts.Password)); 225 | t.Start(); 226 | } 227 | // Wait for max scan time 228 | Thread.Sleep(Host.PORT_MAX_WAIT_MS * 4); 229 | } 230 | else 231 | { 232 | // Enumerate shares 233 | foreach (Host host in hf.ReachableHosts) 234 | { 235 | Thread t = new(() => WindowsShareFinder.DiscoverDeviceShares(host)); 236 | t.Start(); 237 | } 238 | // Wait for max scan time 239 | Thread.Sleep(Host.PORT_MAX_WAIT_MS * 4); 240 | } 241 | 242 | OutputHelper.WriteLine($"probing is complete and we have {hf.HostsWithShares.Count} hosts with accessible shares", 1); 243 | 244 | if (hf.HostsWithShares.Count == 0) 245 | { 246 | OutputHelper.WriteLine("There are no hosts with accessible SMB shares. Exiting..."); 247 | Environment.Exit(0); 248 | } 249 | 250 | if (!opts.Quiet) 251 | { 252 | OutputHelper.WriteLine("reachabled hosts with accessible SMB shares:",2); 253 | foreach (Host host in hf.HostsWithShares) 254 | OutputHelper.WriteLine(host.Address,3); 255 | } 256 | 257 | // Build list of uncPaths from up hosts 258 | List shares = new(); 259 | foreach (Host h in hf.HostsWithShares) 260 | shares.AddRange(h.Shares); 261 | 262 | if (opts.Verbose) 263 | { 264 | OutputHelper.WriteLine("accessible SMB shares:", 2); 265 | foreach (Share share in shares) 266 | OutputHelper.WriteLine(share.uncPath, 3); 267 | } 268 | 269 | if(opts.ExcludeHiddenShares || opts.Shares.Any() || opts.ExcludedShares.Any()) 270 | OutputHelper.WriteLine("6a. Filtering share list"); 271 | 272 | if (opts.Shares.Any()) 273 | { 274 | OutputHelper.WriteLine("Keeping only named shares", 1); 275 | shares = shares 276 | .Where(item => opts.Shares.ToList().ConvertAll(i => i.ToLower()).Contains(item.Name.ToLower())) 277 | .ToList(); 278 | } 279 | 280 | if (opts.ExcludeHiddenShares) 281 | { 282 | OutputHelper.WriteLine("Filtering out hidden shares",1); 283 | shares = shares 284 | .Where(item => !item.Name.EndsWith('$')) 285 | .ToList(); 286 | } 287 | 288 | if (opts.ExcludedShares.Any()) 289 | { 290 | OutputHelper.WriteLine("Filtering out named excluded shares", 1); 291 | shares = shares 292 | .Where(item => ! opts.ExcludedShares.ToList().ConvertAll(i => i.ToLower()).Contains(item.Name.ToLower())) 293 | .ToList(); 294 | } 295 | 296 | if (! shares.Any()) 297 | { 298 | OutputHelper.WriteLine("There are no accessible SMB shares to scan. Exiting..."); 299 | Environment.Exit(0); 300 | } 301 | 302 | if (opts.Verbose) 303 | { 304 | OutputHelper.WriteLine($"Shares found:", 1); 305 | foreach (Share s in shares) 306 | OutputHelper.WriteLine(s.uncPath, 2); 307 | } 308 | 309 | OutputHelper.WriteLine("6. Enumerating accessible shares, this can be slow..."); 310 | 311 | List filePatterns = new List { ".*(password|config|credentials|creds).*", ".*(ps1|bat|vbs|sh|cmd)$" }; 312 | 313 | 314 | if (opts.GrabFiles) 315 | { 316 | OutputHelper.WriteLine($"Grabbing files and storing them in {opts.OutputDirectory}", 1); 317 | if (opts.FilePatterns.Any()) 318 | { 319 | filePatterns = opts.FilePatterns.ToList(); 320 | OutputHelper.WriteLine($"Using the provided regexes", 1); 321 | } 322 | } 323 | else 324 | { 325 | OutputHelper.WriteLine($"NOT Grabbing files - use the '-g' flag to grab them if needed", 1); 326 | } 327 | 328 | // Find files on all the shares 329 | FileFinder 330 | ff = new( 331 | shares: shares, 332 | outputDirectory: opts.OutputDirectory, 333 | filePatterns: filePatterns, 334 | fetchFiles: opts.GrabFiles, 335 | getPermissionsForSingleFileInDir: opts.EnumerateOnlyASingleFilesAcl, 336 | enumerateAcls: !opts.DontEnumerateAcls, 337 | verbose: opts.Verbose, 338 | crossPlatform: crossPlatform 339 | ); 340 | 341 | OutputHelper.WriteLine("7. Completing the writes to CSV or elasticsearch (or both)"); 342 | 343 | OutputHelper.CloseAndFlush(); 344 | 345 | OutputHelper.WriteLine(" -- AUDIT COMPLETE --"); 346 | 347 | 348 | // TODO: know when elasticsearch sink has finished outputting 349 | } 350 | 351 | static void OutputHelp(ParserResult result, IEnumerable errs) 352 | { 353 | OutputHelper.ConsoleWriteLogo(); 354 | HelpText helpText = HelpText.AutoBuild(result, h => 355 | { 356 | //configure help 357 | h.AdditionalNewLineAfterOption = false; 358 | h.Heading = ""; 359 | h.Copyright = "Apache License 2.0"; 360 | return HelpText.DefaultParsingErrorsHandler(result, h); 361 | }, e => e); 362 | Console.WriteLine(helpText); 363 | } 364 | 365 | static void OutputHelp(Exception err) 366 | { 367 | string pad = new('-', err.Message.Length / 2); 368 | OutputHelper.ConsoleWriteLogo(); 369 | Console.WriteLine($"!{pad} ERROR {pad}!"); 370 | Console.WriteLine(""); 371 | Console.WriteLine(" " + err.Message); 372 | Console.WriteLine(""); 373 | Console.WriteLine(" For help use --help"); 374 | Console.WriteLine(""); 375 | Console.WriteLine($"!{pad} ERROR {pad}!"); 376 | System.Environment.Exit(1); 377 | } 378 | 379 | 380 | #region Classes 381 | 382 | public class Options 383 | { 384 | 385 | [Option('c', "csv-file", Group = "output", Required = false, HelpText = "Output results to a CSV file by providing filepath")] 386 | public string CsvFile { get; set; } 387 | 388 | [Option('e', "elasticsearch-host", Group = "output", Required = false, HelpText = "Output results to elasticsearch by providing elasticsearch hostname (default port is 9200 , but can be overridden)")] 389 | public string ElasticsearchHost { get; set; } 390 | 391 | [Option("elasticsearch-port", Required = false, Default = "9200", HelpText = "Define the elasticsearch custom port if required")] 392 | public string ElasticsearchPort { get; set; } 393 | 394 | [Option('f', "fast", Required = false, HelpText = "Enumerate only one files permissions per directory")] 395 | public bool EnumerateOnlyASingleFilesAcl { get; set; } 396 | 397 | [Option('l', "scan-local-shares", Required = false, HelpText = "Scan the local shares on this machine")] 398 | public bool ScanLocalShares { get; set; } 399 | 400 | [Option('D', "disable-network-discovery", Required = false, HelpText = "Disable network discovery")] 401 | public bool DisableNetworkDiscovery { get; set; } 402 | 403 | [Option('n', "network", Required = false, HelpText = "Manually add network to scan (multiple accepted)")] 404 | public IEnumerable Networks { get; set; } 405 | [Option('N', "exclude-network", Required = false, HelpText = "Exclude a network from scanning (multiple accepted)")] 406 | public IEnumerable ExcludedNetworks { get; set; } 407 | 408 | [Option('h', "host", Required = false, HelpText = "Manually add host to scan")] 409 | public IEnumerable Hosts { get; set; } 410 | 411 | [Option('H', "exclude-host", Required = false, HelpText = "Exclude a host from scanning")] 412 | public IEnumerable ExcludedHosts { get; set; } 413 | 414 | [Option('q', "quiet", Required = false, HelpText = "Disable unneccessary output")] 415 | public bool Quiet { get; set; } 416 | 417 | [Option('S', "exclude-share", Required = false, HelpText = "Do not scan shares with this name (multiple accepted)")] 418 | public IEnumerable ExcludedShares { get; set; } 419 | 420 | [Option('s', "share", Required = false, HelpText = "Only scan shares with this name (multiple accepted)")] 421 | public IEnumerable Shares { get; set; } 422 | 423 | [Option("file-pattern", Required = false, HelpText = "Only fetch files matching these regexes patterns")] 424 | public IEnumerable FilePatterns { get; set; } 425 | 426 | [Option('g', "grab-files", Required = false, HelpText = "Grab files and store them locally")] 427 | public bool GrabFiles { get; set; } 428 | 429 | [Option("loot", Required = false, Default = "loot", HelpText = "Path to store grabbed files")] 430 | public string OutputDirectory { get; set; } 431 | 432 | [Option('E', "exclude-hidden-shares", Required = false, HelpText = "Exclude shares ending in $")] 433 | public bool ExcludeHiddenShares { get; set; } 434 | 435 | [Option('v', "verbose", Required = false, HelpText = "Give more output")] 436 | public bool Verbose { get; set; } 437 | 438 | [Option('m', "max-network-cidr-size", Required = false, Default = "20", HelpText = "Maximum network size to scan for SMB Hosts")] 439 | public string MaxNetworkSizeForScanning { get; set; } 440 | 441 | [Option('A', "dont-enumerate-acls", Required = false, Default = false, HelpText = "Skip enumeration of file ACLs")] 442 | public bool DontEnumerateAcls { get; set; } 443 | [Option('d', "domain", Required = false, Default = "", HelpText = "Domain for connecting to SMB")] 444 | public string Domain { get; set; } 445 | 446 | [Option('u', "username", Required = false, HelpText = "Username for connecting to SMB - mandatory on linux")] 447 | public string Username { get; set; } 448 | 449 | [Option('p', "password", Required = false, HelpText = "Password for connecting to SMB - mandatory on linux")] 450 | public string Password { get; set; } 451 | 452 | [Usage(ApplicationAlias = "SMBeagle")] 453 | public static IEnumerable Examples 454 | { 455 | get 456 | { 457 | UnParserSettings unParserSettings = new(); 458 | unParserSettings.PreferShortName = true; 459 | yield return new Example("Output to a CSV file", unParserSettings,new Options { CsvFile = "out.csv" }); 460 | yield return new Example("Output to elasticsearch (Preferred)", unParserSettings, new Options { ElasticsearchHost = "127.0.0.1" }); 461 | yield return new Example("Output to elasticsearch and CSV", unParserSettings, new Options { ElasticsearchHost = "127.0.0.1", CsvFile = "out.csv" }); 462 | yield return new Example("Disable network discovery and provide manual networks", unParserSettings, new Options { ElasticsearchHost = "127.0.0.1", DisableNetworkDiscovery = true, Networks = new List() { "192.168.12.0./23", "192.168.15.0/24" } }); 463 | yield return new Example("Do not enumerate ACLs (FASTER)", unParserSettings, new Options { ElasticsearchHost = "127.0.0.1", DontEnumerateAcls = true }); 464 | } 465 | } 466 | } 467 | 468 | #endregion 469 | } 470 | } 471 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-green.svg)](https://GitHub.com/punk-security/pwnspoof/graphs/commit-activity) 2 | [![Maintaner](https://img.shields.io/badge/maintainer-PunkSecurity-blue)](https://www.punksecurity.co.uk) 3 | [![Docker Pulls](https://img.shields.io/docker/pulls/punksecurity/smbeagle)](https://hub.docker.com/r/punksecurity/smbeagle) 4 | [![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=punk-security_smbeagle&metric=ncloc)](https://sonarcloud.io/summary/new_code?id=punk-security_smbeagle) 5 | [![Bugs](https://sonarcloud.io/api/project_badges/measure?project=punk-security_smbeagle&metric=bugs)](https://sonarcloud.io/summary/new_code?id=punk-security_smbeagle) 6 | [![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=punk-security_smbeagle&metric=vulnerabilities)](https://sonarcloud.io/summary/new_code?id=punk-security_smbeagle) 7 | 8 | ``` 9 | ____ __ _____ _ __ 10 | / __ \__ ______ / /__/ ___/___ _______ _______(_) /___ __ 11 | / /_/ / / / / __ \/ //_/\__ \/ _ \/ ___/ / / / ___/ / __/ / / / 12 | / ____/ /_/ / / / / ,< ___/ / __/ /__/ /_/ / / / / /_/ /_/ / 13 | /_/ \__,_/_/ /_/_/|_|/____/\___/\___/\__,_/_/ /_/\__/\__, / 14 | PRESENTS /____/ 15 | ``` 16 | 17 | # SMBeagle 18 | 19 | SMBeagle is a cross-platform (SMB) fileshare auditing tool that hunts out all files it can see in the network 20 | and reports if the file can be read and/or written. All these findings are streamed out to either 21 | a CSV file or an elasticsearch host, or both!? 🚀 22 | 23 | When running on Windows, with no credentials provided, SMBeagle will make use of the win32 APIs for maximum speed, and integrated auth. 24 | 25 | When running on Linux, or when credentials are provided, we use the cross-platform file scanning through [SMBLibrary](https://github.com/TalAloni/SMBLibrary) 26 | 27 | ## No more digital signing 28 | 29 | SMBeagle up to v3 was digitally signed, but v4 adds more offensive features. Namely, it will now look for an retrieve juicy looking files. Great for pentests, but not something we want to digitally sign! 30 | 31 | If you want the original version, without the file grabbing features, use v3 which is digitally signed. 32 | 33 | It has 2 awesome use cases: 34 | 35 | ### Cast a spotlight on weak share permissions. 36 | Businesses of all sizes often have file shares with awful file permissions. 37 | 38 | Large businesses have sprawling shares on file servers and its not uncommon to find sensitive data with misconfigured permissions. 39 | 40 | Small businesses often have a small NAS in the corner of the office with no restrictions at all! 41 | 42 | SMBeagle crawls these shares and lists out all the files it can read and write. If it can read them, so can ransomware. 43 | 44 | ### Lateral movement and privilege escalation 45 | SMBeagle can provide penetration testers with the less obvious routes to escalate privileges and move laterally. 46 | 47 | By outputting directly into elasticsearch, testers can quickly find readable scripts and writeable executables. 48 | 49 | Finding watering hole attacks and unprotected passwords never felt so easy! 🐱‍👤 50 | 51 | **To make it even easier, we've added the ```-g``` flag which will now fetch files back if they look interesting!** 52 | 53 | What looks interesting? Well by default we look for scripts and filenames with words like password in them. 54 | 55 | ... You can provide your own regexes with the ```--file-pattern``` flag. 56 | 57 | ## Kibana Dashboard 58 | Please see [Kibana readme](Kibana/README.md) for detailed instructions on installing and using the Kibana dashboards which 59 | provide management visuals and makes data pivoting all the easier. 60 | 61 | ## Installation 62 | 63 | ### Docker 64 | * ```docker pull punksecurity/smbeagle``` 65 | 66 | ### Linux 67 | * Go to the latest release https://github.com/punk-security/smbeagle/releases/latest 68 | * Download the linux_amd64.zip or linux_arm64.zip 69 | * Unzip the download and run smbeagle from the terminal 70 | 71 | ### Windows 72 | * Go to the latest release https://github.com/punk-security/smbeagle/releases/latest 73 | * Download the win_x64.zip (only 64bit is supported at the moment) 74 | * Unzip the download and run SMBeagle.exe from a command prompt or powershell terminal 75 | 76 | ## Usage 77 | 78 | The only mandatory parameter is to set an output, which should be either an elasticsearch hosts IP address or a csv file. 79 | 80 | A good starting point is to enable fast mode and output to csv, but this CSV could get huge depending on how many files it finds. 81 | 82 | ``` 83 | ./SMBeagle.exe -c out.csv -f 84 | ``` 85 | 86 | ### Public IP scanning 87 | 88 | The scanning of discovered public hosts and networks is disabled by default as SMBeagle discovers networks from netstat which 89 | includes all current connections such as web browser sessions etc. 90 | 91 | To scan a public network, declare it manually with something like `-n 1.0.0.1/32` or `-n 1.0.0.0/24` 92 | 93 | ### Docker usage 94 | Punk security provides a linux docker image of SMBeagle. 95 | 96 | To get findings out, you will need to mount a folder into the container and tell SMBeagle to save its output to that mount (or use elasticsearch) 97 | 98 | A good starter example is: 99 | 100 | `docker run -v "$(pwd)/output:/tmp/output" punksecurity/smbeagle -c /tmp/output/results.csv -n 10.10.10.0/24` 101 | 102 | Note that network discovery is disabled when running in docker, so make sure you pass the ranges that 103 | you wish to scan with the `-n` command line switch, or hosts will the `-h` switch. 104 | 105 | ### Full Usage 106 | 107 | ``` 108 | USAGE: 109 | Output to a CSV file: 110 | SMBeagle -c out.csv 111 | Output to elasticsearch (Preferred): 112 | SMBeagle -e 127.0.0.1 113 | Output to elasticsearch and CSV: 114 | SMBeagle -c out.csv -e 127.0.0.1 115 | Disable network discovery and provide manual networks: 116 | SMBeagle -D -e 127.0.0.1 -n 192.168.12.0./23 192.168.15.0/24 117 | Do not enumerate ACLs (FASTER): 118 | SMBeagle -A -e 127.0.0.1 119 | 120 | -c, --csv-file (Group: output) Output results to a CSV 121 | file by providing filepath 122 | -e, --elasticsearch-host (Group: output) Output results to 123 | elasticsearch by providing elasticsearch 124 | hostname (default port is 9200 , but can be 125 | overridden) 126 | --elasticsearch-port (Default: 9200) Define the elasticsearch 127 | custom port if required 128 | -f, --fast Enumerate only one files permissions per 129 | directory 130 | -l, --scan-local-shares Scan the local shares on this machine 131 | -D, --disable-network-discovery Disable network discovery 132 | -n, --network Manually add network to scan (multiple 133 | accepted) 134 | -N, --exclude-network Exclude a network from scanning (multiple 135 | accepted) 136 | -h, --host Manually add host to scan 137 | -H, --exclude-host Exclude a host from scanning 138 | -q, --quiet Disable unneccessary output 139 | -S, --exclude-share Do not scan shares with this name (multiple 140 | accepted) 141 | -s, --share Only scan shares with this name (multiple 142 | accepted) 143 | --file-pattern Only fetch files matching these regexes 144 | patterns 145 | -g, --grab-files Grab files and store them locally 146 | --loot (Default: loot) Path to store grabbed files 147 | -E, --exclude-hidden-shares Exclude shares ending in $ 148 | -v, --verbose Give more output 149 | -m, --max-network-cidr-size (Default: 20) Maximum network size to scan 150 | for SMB Hosts 151 | -A, --dont-enumerate-acls (Default: false) Skip enumeration of file 152 | ACLs 153 | -d, --domain (Default: ) Domain for connecting to SMB 154 | -u, --username Username for connecting to SMB - mandatory 155 | on linux 156 | -p, --password Password for connecting to SMB - mandatory 157 | on linux 158 | --help Display this help screen. 159 | --version Display version information. 160 | 161 | ``` 162 | 163 | ## Architecture 164 | 165 | SMBeagle does a lot of work, which is broken down into loosely coupled modules which hand off to each other. 166 | This keeps the design simple and allows us to extend each module easily. 167 | 168 | In summary it: 169 | 170 | * Looks at your local machine for network connections and adapters 171 | * Takes all those private adaptors and connections and builds a list of private network candidates 172 | * Scans those networks for TCP port 445 173 | * Scans all detected SMB servers for accessible shares 174 | * Inventories all those shares for files and checks Read, Write, Delete permissions 175 | 176 | ![Schematic](Docs/schematic.png) 177 | -------------------------------------------------------------------------------- /SMBeagle.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net9.0 6 | 4.0.0 7 | 8 | 9 | 10 | 3 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /SMBeagle.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.31019.35 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SMBeagle", "SMBeagle.csproj", "{D21DBF8D-0BB4-491C-B400-32EEACD16792}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {D21DBF8D-0BB4-491C-B400-32EEACD16792}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {D21DBF8D-0BB4-491C-B400-32EEACD16792}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {D21DBF8D-0BB4-491C-B400-32EEACD16792}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {D21DBF8D-0BB4-491C-B400-32EEACD16792}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {2BA0DE59-D954-42AB-9C3C-6FCEE3E0F0C1} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /ShareDiscovery/CrossPlatformShareFinder.cs: -------------------------------------------------------------------------------- 1 | using SMBeagle.HostDiscovery; 2 | using SMBeagle.Output; 3 | using SMBLibrary; 4 | using SMBLibrary.Client; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Net; 8 | 9 | namespace SMBeagle.ShareDiscovery 10 | { 11 | class CrossPlatformShareFinder 12 | { 13 | private static bool AttemptClientLogin(ISMBClient client, Host host, string domain, string username, string password) 14 | { 15 | if (client.Connect(IPAddress.Parse(host.Address), SMBTransportType.DirectTCPTransport) && 16 | client.Login(domain, username, password) == NTStatus.STATUS_SUCCESS) 17 | { 18 | return true; 19 | } 20 | if (client.Connect(IPAddress.Parse(host.Address), SMBTransportType.NetBiosOverTCP) && 21 | client.Login(domain, username, password) == NTStatus.STATUS_SUCCESS) 22 | { 23 | return true; 24 | } 25 | return false; 26 | } 27 | public static bool GetClient(Host host, string domain, string username, string password) 28 | { 29 | ISMBClient client; 30 | client = new SMB2Client(); 31 | if (AttemptClientLogin(client, host, domain, username, password)) 32 | { 33 | //SMB2 34 | host.Client = client; 35 | return true; 36 | } 37 | client = new SMB1Client(); 38 | if (AttemptClientLogin(client, host, domain, username, password)) 39 | { 40 | //SMB1 41 | host.Client = client; 42 | return true; 43 | } 44 | return false; 45 | } 46 | 47 | private static List GetDeviceShares(Host host) 48 | { 49 | NTStatus returnCode; 50 | List shares = host.Client.ListShares(out returnCode); 51 | if (returnCode == NTStatus.STATUS_SUCCESS) 52 | return shares; 53 | else 54 | { 55 | OutputHelper.WriteLine($"Could not list shares from device - ERROR CODE: '{ returnCode }' for host '{host}'"); 56 | return new List(); 57 | } 58 | } 59 | 60 | public static void DiscoverDeviceShares(Host host) 61 | { 62 | if (host.Client == null) 63 | { 64 | OutputHelper.WriteLine($"Error: No SMBClient connection established for this host '{host}'"); 65 | return; 66 | } 67 | List shares = GetDeviceShares(host); 68 | foreach (String s in shares) 69 | { 70 | Share share = new Share(host, s, Enums.ShareTypeEnum.DISK); 71 | host.Shares.Add(share); 72 | } 73 | } 74 | 75 | public static void DiscoverDeviceShares(Host host, string domain, string username, string password) 76 | { 77 | if (GetClient(host, domain, username, password)) 78 | DiscoverDeviceShares(host); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /ShareDiscovery/Share.cs: -------------------------------------------------------------------------------- 1 | using SMBeagle.Enums; 2 | using SMBeagle.HostDiscovery; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace SMBeagle.ShareDiscovery 10 | { 11 | class Share 12 | { 13 | public Host Host { get; set; } 14 | public string Name { get; set; } 15 | public ShareTypeEnum Type { get; set; } 16 | public Share(Host host, string name, ShareTypeEnum type) 17 | { 18 | Host = host; 19 | Name = name; 20 | Type = type; 21 | } 22 | public string uncPath 23 | { 24 | get 25 | { 26 | return $@"\\{Host.Address}\{Name}\".ToLower(); 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /ShareDiscovery/WindowsShareFinder.cs: -------------------------------------------------------------------------------- 1 | using SMBeagle.HostDiscovery; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Runtime.InteropServices; 5 | using System.Text; 6 | 7 | namespace SMBeagle.ShareDiscovery 8 | { 9 | class WindowsShareFinder 10 | { 11 | // https://github.com/mitchmoser/SharpShares/blob/master/SharpShares/Enums/Shares.cs 12 | [DllImport("Netapi32.dll", SetLastError = true)] 13 | public static extern int NetWkstaGetInfo(string servername, int level, out IntPtr bufptr); 14 | 15 | [DllImport("Netapi32.dll", SetLastError = true)] 16 | static extern int NetApiBufferFree(IntPtr Buffer); 17 | 18 | [DllImport("Netapi32.dll", CharSet = CharSet.Unicode)] 19 | private static extern int NetShareEnum( 20 | StringBuilder ServerName, 21 | int level, 22 | ref IntPtr bufPtr, 23 | uint prefmaxlen, 24 | ref int entriesread, 25 | ref int totalentries, 26 | ref int resume_handle 27 | ); 28 | 29 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] 30 | public struct SHARE_INFO_1 31 | { 32 | public string shi1_netname; 33 | public uint shi1_type; 34 | public string shi1_remark; 35 | public SHARE_INFO_1(string sharename, uint sharetype, string remark) 36 | { 37 | this.shi1_netname = sharename; 38 | this.shi1_type = sharetype; 39 | this.shi1_remark = remark; 40 | } 41 | public override string ToString() 42 | { 43 | return shi1_netname; 44 | } 45 | } 46 | 47 | private enum NetError : uint 48 | { 49 | NERR_Success = 0, 50 | NERR_BASE = 2100, 51 | NERR_UnknownDevDir = (NERR_BASE + 16), 52 | NERR_DuplicateShare = (NERR_BASE + 18), 53 | NERR_BufTooSmall = (NERR_BASE + 23), 54 | } 55 | 56 | private enum SHARE_TYPE : uint 57 | { 58 | STYPE_DISKTREE = 0x00000000, 59 | STYPE_PRINTQ = 0x00000001, 60 | STYPE_DEVICE = 0x00000002, 61 | STYPE_IPC = 0x00000003, 62 | STYPE_CLUSTER_FS = 0x02000000, 63 | STYPE_CLUSTER_SOFS = 0x04000000, 64 | STYPE_CLUSTER_DFS = 0x08000000, 65 | STYPE_SPECIAL = 0x80000000, 66 | } 67 | 68 | const uint MAX_PREFERRED_LENGTH = 0xFFFFFFFF; 69 | 70 | public static List EnumNetShares(Host host) 71 | { 72 | List ShareInfos = new List(); 73 | int entriesread = 0, totalentries = 0, resume_handle = 0; 74 | int nStructSize = Marshal.SizeOf(typeof(SHARE_INFO_1)); 75 | IntPtr bufPtr = IntPtr.Zero; 76 | StringBuilder server = new StringBuilder(host.Address); 77 | int ret = NetShareEnum(server, 1, ref bufPtr, MAX_PREFERRED_LENGTH, ref entriesread, ref totalentries, ref resume_handle); 78 | if (ret == (int)NetError.NERR_Success) 79 | { 80 | IntPtr currentPtr = bufPtr; 81 | for (int i = 0; i < entriesread; i++) 82 | { 83 | SHARE_INFO_1 shi1 = (SHARE_INFO_1)Marshal.PtrToStructure(currentPtr, typeof(SHARE_INFO_1)); 84 | ShareInfos.Add(shi1); 85 | currentPtr += nStructSize; 86 | } 87 | NetApiBufferFree(bufPtr); 88 | } 89 | return ShareInfos; 90 | } 91 | 92 | public static void DiscoverDeviceShares(Host host) 93 | { 94 | List shareInfos = EnumNetShares(host); 95 | foreach (SHARE_INFO_1 si in shareInfos) 96 | { 97 | Share share = ConvertShareInfoToShare(host, si); 98 | if (share != null) 99 | host.Shares.Add(share); 100 | } 101 | } 102 | 103 | private static Share ConvertShareInfoToShare(Host host, SHARE_INFO_1 shareInfo) 104 | { 105 | switch(shareInfo.shi1_type) 106 | { 107 | case (uint)SHARE_TYPE.STYPE_CLUSTER_DFS: 108 | return new Share(host, shareInfo.shi1_netname, Enums.ShareTypeEnum.DFS_SHARE); 109 | case (uint)SHARE_TYPE.STYPE_CLUSTER_FS: 110 | return new Share(host, shareInfo.shi1_netname, Enums.ShareTypeEnum.CLUSTER_SHARE); 111 | case (uint)SHARE_TYPE.STYPE_CLUSTER_SOFS: 112 | return new Share(host, shareInfo.shi1_netname, Enums.ShareTypeEnum.SCALE_OUT_CLUSTER_SHARE); 113 | default: 114 | return new Share(host, shareInfo.shi1_netname, Enums.ShareTypeEnum.DISK); 115 | } 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /tests/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM punksecurity/smbeagle 2 | RUN apt update && apt install python3 python3-pip -y 3 | RUN pip install impacket pytest 4 | RUN mkdir /empty_dir 5 | WORKDIR /tests/ 6 | COPY tests/* . 7 | ENTRYPOINT [ "" ] 8 | CMD ["pytest", "-v"] 9 | -------------------------------------------------------------------------------- /tests/Dockerfile.Linux: -------------------------------------------------------------------------------- 1 | FROM punksecurity/smbeagle 2 | RUN apt update && apt install python3 python3-pip -y 3 | RUN pip install impacket pytest 4 | RUN mkdir /empty_dir 5 | WORKDIR /tests/ 6 | COPY tests/* . 7 | ENTRYPOINT [ "" ] 8 | ENV ROOTDIR "/" 9 | CMD pytest -v -k 'not on_windows' 10 | -------------------------------------------------------------------------------- /tests/Dockerfile.Windows: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/windows:20H2 2 | ENV PYTHON_VERSION 3.10.5 3 | ENV PYTHON_GET_PIP_URL https://bootstrap.pypa.io/get-pip.py 4 | 5 | COPY windows_scripts/* / 6 | RUN "powershell -noprofile -executionpolicy bypass -file .\install_python.ps1" 7 | 8 | RUN "pip install impacket pytest" 9 | 10 | ENV ROOTDIR "C:\\" 11 | WORKDIR "C:\\" 12 | RUN mkdir empty_dir tests 13 | COPY tests tests 14 | COPY x64 "C:\\windows\\system32\\." 15 | WORKDIR tests 16 | CMD pytest -v -k "not on_linux" 17 | 18 | # Cant test native auth as windows auth broken in containers... tried this hacky fix but no good 19 | #RUN net user /add test 20 | #RUN net localgroup administrators test /add 21 | #USER test 22 | #ENV NATIVE_AUTH=1 23 | #RUN net user test goose; pytest -k test_fifty_files_in_the_root 24 | -------------------------------------------------------------------------------- /tests/tests/helpers.py: -------------------------------------------------------------------------------- 1 | from impacket import smbserver 2 | import multiprocessing 3 | import os 4 | import subprocess 5 | from time import sleep 6 | import csv 7 | import shutil 8 | import uuid 9 | 10 | 11 | def __setupSMB(address, dir, SMB2 = True): 12 | os.chdir(f"{os.environ['ROOTDIR']}empty_dir") 13 | server = smbserver.SimpleSMBServer(listenAddress=address, listenPort=445) 14 | server.addShare("share", dir, "") 15 | server.addCredential("test", 1200, "9FD78381EC915F1AAAD3B435B51404EE", "25EDEDFF26CB970623DDA4733227A3F7") 16 | server.setSMB2Support(SMB2) 17 | server.setLogFile('') 18 | server.start() 19 | 20 | def setupSMB(address, dir): 21 | process = multiprocessing.Process(target=__setupSMB, args=[address, dir]) 22 | process.start() 23 | return process 24 | 25 | class SMB(object): 26 | def __init__(self, address = "0.0.0.0", dir_structure = ["fileA", "fileB"]): 27 | self.address = address 28 | self.dir_structure = dir_structure 29 | self.dir = f"{os.environ['ROOTDIR']}{uuid.uuid4().hex}" 30 | def __enter__(self): 31 | self.smb = setupSMB(self.address, self.dir) 32 | os.mkdir(self.dir) 33 | self.populate_dir(self.dir, self.dir_structure) 34 | def populate_dir(self, dir, dir_structure): 35 | for item in dir_structure: 36 | if type(item) != type( () ) and type(item) != type(""): 37 | raise ValueError("Directory should be list of strings and tuples") 38 | if type(item) == type( () ): 39 | #type tuple, so create folder and then parse that structure 40 | os.mkdir(f"{dir}{os.sep}{item[0]}") 41 | self.populate_dir(f"{dir}{os.sep}{item[0]}", item[1]) 42 | else: 43 | # type string, so make the file 44 | open(f"{dir}{os.sep}{item}", 'a').close() 45 | 46 | def __exit__(self, *args, **kwargs): 47 | self.smb.kill() 48 | sleep(1) 49 | shutil.rmtree(self.dir) 50 | #self.smb.close() 51 | 52 | def runSMBeagle(*args, print_out=True): 53 | run = subprocess.run(["smbeagle",*args], stdout = subprocess.PIPE, universal_newlines=True) 54 | if print_out: 55 | print(run.stdout) 56 | return run.stdout 57 | 58 | def runSMBeagleToCSV(*args): 59 | return runSMBeagle("-c","out.csv",*args) 60 | 61 | def runSMBeagleQuick(*args): 62 | return runSMBeagleToCSV("-D",*args) 63 | 64 | def runSMBeagleToCSVWithAuth(*args): 65 | try: 66 | os.environ["NATIVE_AUTH"] 67 | return runSMBeagleToCSV(*args) 68 | except: 69 | return runSMBeagleToCSV("-u","test", "-p", "goose", *args) 70 | 71 | def runSMBeagleToCSVWithAuthAndReturnResults(*args): 72 | print(runSMBeagleToCSVWithAuth(*args)) 73 | with open('out.csv', newline='') as csvfile: 74 | results = list(csv.DictReader(csvfile, delimiter=',', quotechar='"')) 75 | for result in results: 76 | print(result) 77 | return results 78 | -------------------------------------------------------------------------------- /tests/tests/test_010_options.py: -------------------------------------------------------------------------------- 1 | from helpers import * 2 | 3 | username_or_password_missing_error = "ERROR: Username and Password required on none Windows platforms" 4 | def test_username_and_password_required_on_linux(): 5 | assert username_or_password_missing_error in runSMBeagleQuick() 6 | def test_password_required_on_linux(): 7 | assert username_or_password_missing_error in runSMBeagleQuick("-p","goose") 8 | def test_username_required_on_linux(): 9 | assert username_or_password_missing_error in runSMBeagleQuick("-u","goose") 10 | def test_username_and_password_not_required_on_windows(): 11 | assert username_or_password_missing_error not in runSMBeagleQuick() 12 | def test_username_and_password_accepted(): 13 | assert username_or_password_missing_error not in runSMBeagleQuick("-u","goose", "-p", "goose") 14 | def test_long_username_accepted(): 15 | assert username_or_password_missing_error not in runSMBeagleQuick("--username","goose", "-p", "goose") 16 | def test_long_password_accepted(): 17 | assert username_or_password_missing_error not in runSMBeagleQuick("-u","goose", "--password", "goose") 18 | 19 | output_required_error = "At least one option from group 'output' (c, csv-file, e, elasticsearch-host)" 20 | def test_csv_or_elasticsearch_required(): 21 | assert output_required_error in runSMBeagle() 22 | def test_short_csv_accepted(): 23 | assert output_required_error not in runSMBeagleQuick("-c","out.csv") 24 | def test_long_csv_accepted(): 25 | assert output_required_error not in runSMBeagleQuick("--csv-file","out.csv") 26 | def test_short_elasticsearch_accepted(): 27 | assert output_required_error not in runSMBeagleQuick("-e","elasticsearch") 28 | def test_long_elasticsearch_accepted(): 29 | assert output_required_error not in runSMBeagleQuick("--elasticsearch-host","elasticsearch") 30 | 31 | 32 | def test_manual_host_accepted(): 33 | assert "127.0.0.2" in runSMBeagleToCSVWithAuth("-D","-h", "127.0.0.2") 34 | def test_multiple_manual_host_accepted(): 35 | output = runSMBeagleToCSVWithAuth("-D","-h", "127.0.0.2", "127.0.0.3") 36 | assert "127.0.0.2" in output and "127.0.0.3" in output 37 | 38 | def test_manual_network_accepted(): 39 | output = runSMBeagleToCSVWithAuth("-D", "-n", "127.0.0.0/24") 40 | assert "127.0.0.0/24" in output 41 | def test_multiple_manual_network_accepted(): 42 | output = runSMBeagleToCSVWithAuth("-D","-n", "127.0.0.0/24", "127.0.1.0/24") 43 | assert "127.0.0.0/24" in output and "127.0.1.0/24" in output 44 | -------------------------------------------------------------------------------- /tests/tests/test_020_tcp_scans.py: -------------------------------------------------------------------------------- 1 | from helpers import * 2 | 3 | smb_reachable_message = "we have {} hosts with reachable SMB services" 4 | 5 | no_smb_service_discovered_message = smb_reachable_message.format(0) 6 | one_smb_service_discovered_message = smb_reachable_message.format(1) 7 | two_smb_service_discovered_message = smb_reachable_message.format(2) 8 | three_smb_service_discovered_message = smb_reachable_message.format(3) 9 | four_smb_service_discovered_message = smb_reachable_message.format(4) 10 | 11 | def test_one_manual_host_tcp_success(): 12 | with SMB(): 13 | assert one_smb_service_discovered_message in runSMBeagleToCSVWithAuth("-D", "-h", "127.0.0.2") 14 | 15 | def test_one_manual_host_tcp_fail_if_not_listening(): 16 | with SMB("127.0.0.2"): 17 | assert no_smb_service_discovered_message in runSMBeagleToCSVWithAuth("-D", "-h", "127.0.0.3") 18 | 19 | def test_two_manual_host_tcp_success(): 20 | with SMB("127.0.0.2"): 21 | with SMB("127.0.0.3"): 22 | assert two_smb_service_discovered_message in runSMBeagleToCSVWithAuth("-D", "-h", "127.0.0.2", "127.0.0.3") 23 | 24 | def test_one_manual_host_tcp_success_and_not_two_if_second_not_listening(): 25 | with SMB("127.0.0.2"): 26 | assert one_smb_service_discovered_message in runSMBeagleToCSVWithAuth("-D", "-h", "127.0.0.2", "127.0.0.3") 27 | 28 | def test_one_discovered_host_tcp_success(): 29 | with SMB("127.0.0.2"): 30 | assert one_smb_service_discovered_message in runSMBeagleToCSVWithAuth("-D", "-n", "127.0.0.0/24") 31 | 32 | def test_no_discovered_host_when_filtered(): 33 | with SMB("127.0.0.2"): 34 | assert no_smb_service_discovered_message in runSMBeagleToCSVWithAuth("-D", "-n", "127.0.0.0/24","-H","127.0.0.2" ) 35 | 36 | def test_one_discovered_host_when_one_filtered(): 37 | with SMB("127.0.0.2"): 38 | with SMB("127.0.0.3"): 39 | assert one_smb_service_discovered_message in runSMBeagleToCSVWithAuth("-D", "-n", "127.0.0.0/24","-H","127.0.0.2" ) 40 | 41 | def test_two_discovered_host_tcp_success(): 42 | with SMB("127.0.0.2"): 43 | with SMB("127.0.0.3"): 44 | assert two_smb_service_discovered_message in runSMBeagleToCSVWithAuth("-D", "-n", "127.0.0.0/24") 45 | 46 | def test_three_discovered_host_tcp_success(): 47 | with SMB("127.0.0.2"): 48 | with SMB("127.0.0.3"): 49 | with SMB("127.0.0.4"): 50 | assert three_smb_service_discovered_message in runSMBeagleToCSVWithAuth("-D", "-n", "127.0.0.0/24") 51 | 52 | def test_four_discovered_host_tcp_success(): 53 | with SMB("127.0.0.2"): 54 | with SMB("127.0.0.3"): 55 | with SMB("127.0.0.4"): 56 | with SMB("127.0.0.5"): 57 | assert four_smb_service_discovered_message in runSMBeagleToCSVWithAuth("-D", "-n", "127.0.0.0/24") 58 | 59 | def test_disable_network_discovery(): 60 | no_networks_to_scan_message = "there are no networks or hosts to scan" 61 | assert no_networks_to_scan_message in runSMBeagleToCSVWithAuth("-D") 62 | -------------------------------------------------------------------------------- /tests/tests/test_030_output.py: -------------------------------------------------------------------------------- 1 | from helpers import * 2 | 3 | def test_no_acl_mode_returns_false_perms(): 4 | with SMB(dir_structure=["fileA","fileB","fileC"]): 5 | for result in runSMBeagleToCSVWithAuthAndReturnResults("-h", "127.0.0.2", "-A"): 6 | print(result) 7 | # assert perms are all false 8 | assert result["Readable"] == 'False' 9 | assert result["Writeable"] == 'False' 10 | assert result["Deletable"] == 'False' 11 | 12 | ### test fast mode gives matching perms 13 | 14 | def test_csv_fields_exist(): 15 | with SMB(dir_structure=["fileA"]): 16 | fields = ['Name','Host', 'Extension', 'Username', 'Hostname', 'UNCDirectory', 'CreationTime', 'LastWriteTime', 'Readable', 'Writeable', 'Deletable', 'DirectoryType', 'Base'] 17 | for result in runSMBeagleToCSVWithAuthAndReturnResults("-h", "127.0.0.2", "-A"): 18 | for field in fields: 19 | assert field in result.keys() 20 | 21 | def test_csv_fields_are_valid(): 22 | with SMB(dir_structure=[("dirA",["fileA.txt"])]): 23 | fields = ['Name','Host', 'Extension', 'Username', 'Hostname', 'UNCDirectory', 'CreationTime', 'LastWriteTime', 'Readable', 'Writeable', 'Deletable', 'DirectoryType', 'Base'] 24 | for result in runSMBeagleToCSVWithAuthAndReturnResults("-h", "127.0.0.2"): 25 | print(result) 26 | assert result["Name"].lower() == "filea.txt" 27 | assert result["Extension"].lower() == "txt" 28 | assert result["Host"] == "127.0.0.2" 29 | assert result["DirectoryType"] == "SMB" 30 | assert result["UNCDirectory"].lower() == "\\\\127.0.0.2\\share\\dira" 31 | assert result["Base"].lower() == "\\\\127.0.0.2\\share\\" 32 | assert result["Readable"] == 'True' 33 | assert result["Writeable"] == 'True' 34 | assert result["Deletable"] == 'True' 35 | -------------------------------------------------------------------------------- /tests/tests/test_040_smb_scans.py: -------------------------------------------------------------------------------- 1 | from time import time 2 | from helpers import * 3 | 4 | one_file = ["fileA"] 5 | two_files = ["fileA", "fileB"] 6 | no_files = [] 7 | empty_dir = [("emptyDir", [])] 8 | dir_with_one_file = [("dirA", one_file)] 9 | dir_with_two_files = [("dirB", two_files)] 10 | empty_dir_with_empty_dir = [("emptyDir", empty_dir)] 11 | empty_dir_with_empty_dir_nested = [("emptyDir", empty_dir_with_empty_dir)] 12 | two_files_in_two_nested_dirs = [("dirA", [("dirB", two_files)])] 13 | 14 | detected_share_message = r"Enumerating all subdirectories for '\\{host}\{share}\'" 15 | 16 | def test_detect_normal_share(): 17 | with SMB(): 18 | assert detected_share_message.format(host = "127.0.0.2", share = "share") in runSMBeagleToCSVWithAuth("-h","127.0.0.2") 19 | 20 | def test_detect_admin_share(): 21 | with SMB(): 22 | assert detected_share_message.format(host = "127.0.0.2", share = "ipc$") in runSMBeagleToCSVWithAuth("-h","127.0.0.2") 23 | 24 | def test_do_not_detect_admin_share(): 25 | with SMB(): 26 | assert detected_share_message.format(host = "127.0.0.2", share = "ipc$") not in runSMBeagleToCSVWithAuth("-h","127.0.0.2","-E") 27 | 28 | def test_do_not_detect_none_matching_share(): 29 | with SMB(): 30 | assert detected_share_message.format(host = "127.0.0.2", share = "share") not in runSMBeagleToCSVWithAuth("-h","127.0.0.2","-s","goose") 31 | 32 | def test_detect_matching_share(): 33 | with SMB(): 34 | assert detected_share_message.format(host = "127.0.0.2", share = "share") in runSMBeagleToCSVWithAuth("-h","127.0.0.2","-s","share") 35 | 36 | def test_detect_matching_share_from_multiple(): 37 | with SMB(): 38 | assert detected_share_message.format(host = "127.0.0.2", share = "share") in runSMBeagleToCSVWithAuth("-h","127.0.0.2","-s","goose","share") 39 | 40 | def test_one_host_no_files(): 41 | with SMB(dir_structure=no_files): 42 | assert len(runSMBeagleToCSVWithAuthAndReturnResults("-h", "127.0.0.2")) == 0 43 | 44 | def test_one_host_empty_dir(): 45 | with SMB(dir_structure=empty_dir): 46 | assert len(runSMBeagleToCSVWithAuthAndReturnResults("-h", "127.0.0.2")) == 0 47 | 48 | def test_one_host_empty_dir_nested(): 49 | with SMB(dir_structure=empty_dir_with_empty_dir): 50 | assert len(runSMBeagleToCSVWithAuthAndReturnResults("-h", "127.0.0.2")) == 0 51 | 52 | def test_one_host_empty_dir_nested_twice(): 53 | with SMB(dir_structure=empty_dir_with_empty_dir_nested): 54 | assert len(runSMBeagleToCSVWithAuthAndReturnResults("-h", "127.0.0.2")) == 0 55 | 56 | def test_one_host_one_file(): 57 | with SMB(dir_structure=one_file): 58 | assert len(runSMBeagleToCSVWithAuthAndReturnResults("-h", "127.0.0.2")) == 1 59 | 60 | def test_one_host_two_files(): 61 | with SMB(dir_structure=two_files): 62 | assert len(runSMBeagleToCSVWithAuthAndReturnResults("-h", "127.0.0.2")) == 2 63 | 64 | def test_one_host_empty_dir_and_two_files(): 65 | with SMB(dir_structure=(empty_dir + two_files)): 66 | assert len(runSMBeagleToCSVWithAuthAndReturnResults("-h", "127.0.0.2")) == 2 67 | 68 | def test_one_host_one_dir_and_one_file(): 69 | with SMB(dir_structure=dir_with_one_file): 70 | assert len(runSMBeagleToCSVWithAuthAndReturnResults("-h", "127.0.0.2")) == 1 71 | 72 | def test_one_host_one_dir_and_two_files(): 73 | with SMB(dir_structure=dir_with_two_files): 74 | assert len(runSMBeagleToCSVWithAuthAndReturnResults("-h", "127.0.0.2")) == 2 75 | 76 | def test_one_host_one_dir_and_one_file_and_another_root_file(): 77 | with SMB(dir_structure=dir_with_one_file + one_file): 78 | assert len(runSMBeagleToCSVWithAuthAndReturnResults("-h", "127.0.0.2")) == 2 79 | 80 | def test_one_host_two_dirs_with_three_files_and_another_root_file(): 81 | with SMB(dir_structure=dir_with_one_file + dir_with_two_files + one_file): 82 | assert len(runSMBeagleToCSVWithAuthAndReturnResults("-h", "127.0.0.2")) == 4 83 | 84 | def test_one_host_two_dirs_with_three_files_and_another_two_root_files(): 85 | with SMB(dir_structure=dir_with_one_file + dir_with_two_files + two_files): 86 | assert len(runSMBeagleToCSVWithAuthAndReturnResults("-h", "127.0.0.2")) == 5 87 | 88 | def test_one_host_two_dirs_with_three_files(): 89 | with SMB(dir_structure=dir_with_one_file + dir_with_two_files): 90 | assert len(runSMBeagleToCSVWithAuthAndReturnResults("-h", "127.0.0.2")) == 3 91 | 92 | def test_one_host_two_files_in_nested_dirs(): 93 | with SMB(dir_structure=two_files_in_two_nested_dirs): 94 | assert len(runSMBeagleToCSVWithAuthAndReturnResults("-h", "127.0.0.2")) == 2 95 | 96 | def n_files(n): 97 | return [f"a{x}" for x in range(0,n)] 98 | 99 | def smb_with_n_files(n): 100 | with SMB(dir_structure=n_files(n)): 101 | assert len(runSMBeagleToCSVWithAuthAndReturnResults("-h", "127.0.0.2")) == n 102 | 103 | def test_ten_files_in_the_root(): 104 | smb_with_n_files(10) 105 | 106 | def test_fifty_files_in_the_root(): 107 | smb_with_n_files(50) 108 | 109 | def test_one_hundred_files_in_the_root(): 110 | smb_with_n_files(100) 111 | 112 | def test_five_hundred_files_in_the_root(): 113 | smb_with_n_files(500) 114 | 115 | def test_nine_hundred_files_in_the_root(): 116 | smb_with_n_files(900) 117 | 118 | # FAILS 119 | #def test_one_thousand_files_in_the_root(): 120 | # smb_with_n_files(1000) 121 | 122 | def n_files_in_n_dirs(files, dirs): 123 | return [(f"dir{x}", n_files(files)) for x in range(0,dirs)] 124 | 125 | def smb_with_n_files_in_n_dirs(files, dirs): 126 | with SMB(dir_structure=n_files_in_n_dirs(files, dirs)): 127 | assert len(runSMBeagleToCSVWithAuthAndReturnResults("-h", "127.0.0.2", "-A")) == files * dirs 128 | 129 | def test_ten_files_in_two_folders(): 130 | smb_with_n_files_in_n_dirs(10,2) 131 | 132 | def test_fifty_files_in_two_folders(): 133 | smb_with_n_files_in_n_dirs(50,2) 134 | 135 | def test_one_hundred_files_in_two_folders(): 136 | smb_with_n_files_in_n_dirs(100,2) 137 | 138 | def test_five_hundred_files_in_two_folders(): 139 | smb_with_n_files_in_n_dirs(500,2) 140 | 141 | def test_ten_files_in_five_folders(): 142 | smb_with_n_files_in_n_dirs(10,5) 143 | 144 | def test_fifty_files_in_five_folders(): 145 | smb_with_n_files_in_n_dirs(50,5) 146 | 147 | def test_one_hundred_files_in_five_folders(): 148 | smb_with_n_files_in_n_dirs(100,5) 149 | 150 | def test_five_hundred_files_in_five_folders(): 151 | smb_with_n_files_in_n_dirs(500,5) 152 | 153 | def test_five_hundred_files_in_one_hundred_folders(): 154 | smb_with_n_files_in_n_dirs(500,100) 155 | 156 | def test_fast_mode_is_faster(): 157 | start = time() 158 | with SMB(dir_structure=n_files_in_n_dirs(50, 5)): 159 | runSMBeagleToCSVWithAuthAndReturnResults("-h", "127.0.0.2") 160 | time_to_run_normal = time() - start 161 | start = time() 162 | with SMB(dir_structure=n_files_in_n_dirs(50, 5)): 163 | runSMBeagleToCSVWithAuthAndReturnResults("-h", "127.0.0.2", "-f") 164 | time_to_run_fast = time() - start 165 | assert time_to_run_normal > time_to_run_fast 166 | 167 | def test_no_acl_mode_is_faster_than_fast(): 168 | start = time() 169 | with SMB(dir_structure=n_files_in_n_dirs(50, 5)): 170 | runSMBeagleToCSVWithAuthAndReturnResults("-h", "127.0.0.2", "-f") 171 | time_to_run_fast = time() - start 172 | start = time() 173 | with SMB(dir_structure=n_files_in_n_dirs(50, 5)): 174 | runSMBeagleToCSVWithAuthAndReturnResults("-h", "127.0.0.2", "-A") 175 | time_to_run_no_acl = time() - start 176 | assert time_to_run_fast > time_to_run_no_acl 177 | 178 | def n_files_in_dir_n_deep(files, depth): 179 | dir = [(f"dir", n_files(files))] 180 | for x in range(0,depth): 181 | dir = [(f"dir{x}", dir)] 182 | return dir 183 | 184 | def smb_with_n_files_at_depth_n(n, depth): 185 | with SMB(dir_structure=n_files_in_dir_n_deep(n, depth)): 186 | assert len(runSMBeagleToCSVWithAuthAndReturnResults("-h", "127.0.0.2", "-A")) == n 187 | 188 | def test_ten_files_one_folders_deep(): 189 | smb_with_n_files_at_depth_n(10,1) 190 | 191 | def test_ten_files_two_folders_deep(): 192 | smb_with_n_files_at_depth_n(10,2) 193 | 194 | def test_ten_files_three_folders_deep(): 195 | smb_with_n_files_at_depth_n(10,3) 196 | 197 | def test_ten_files_ten_folders_deep(): 198 | smb_with_n_files_at_depth_n(10,10) 199 | -------------------------------------------------------------------------------- /tests/windows_scripts/install_python.ps1: -------------------------------------------------------------------------------- 1 | $url = ('https://www.python.org/ftp/python/{0}/python-{0}-amd64.exe' -f $env:PYTHON_VERSION) 2 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 3 | Invoke-WebRequest -Uri $url -OutFile 'python.exe'; 4 | # https://docs.python.org/3.7/using/windows.html#installing-without-ui 5 | Start-Process python.exe -Wait -ArgumentList @( 6 | '/quiet', 7 | 'InstallAllUsers=1', 8 | 'TargetDir=C:\Python', 9 | 'PrependPath=1', 10 | 'Shortcuts=0', 11 | 'Include_doc=0', 12 | 'Include_pip=1', 13 | 'Include_test=0' 14 | ); 15 | #the installer updated PATH, so we should refresh our local value 16 | Remove-Item python.exe -Force 17 | --------------------------------------------------------------------------------