├── .github └── workflows │ ├── build.yaml │ ├── codeql.yml │ ├── hawkscan-manual.yml │ ├── hawkscan.yml │ └── release.yaml ├── .gitignore ├── Dockerfile ├── README.md ├── azure-pipelines.yml ├── build.gradle.kts ├── ci-examples ├── azure-devops │ └── azure-pipelines.yml └── github │ └── hawkscan.yml ├── docker-compose.yml ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── hawkscripts ├── README.md ├── active │ └── fuzzer.kts ├── authentication │ ├── form-auth-multi.kts │ └── okta-client-credentials-basic.kts ├── hawkscripts.gradle.kts ├── help-images │ ├── intellij-new-project-1.png │ ├── intellij-new-project-2.png │ ├── intellij-new-project-3.png │ └── intellij-new-project-4.png ├── httpsender │ ├── custom-sender.kts │ └── log-http-payloads.kts └── session │ └── jwt-session.kts ├── integration_tests └── conf_files │ └── stackhawk-jsv-json-token.yml ├── javaspringvulny_postman_collection.json ├── local-hawk.sh ├── openapi.json ├── openapi.yaml ├── scan-rules.conf ├── scripts ├── basic-auth.sh ├── json-auth.sh └── multi-cookie-auth.sh ├── settings.gradle.kts ├── src └── main │ ├── java │ └── hawk │ │ ├── Application.java │ │ ├── Config.java │ │ ├── MultiHttpSecurityConfig.java │ │ ├── api │ │ ├── AuthenticationRequest.java │ │ ├── ExtraAuthenticationRequest.java │ │ ├── SearchResult.java │ │ ├── basic │ │ │ └── BasicAuthItemController.java │ │ ├── jwt │ │ │ ├── JwtAuthController.java │ │ │ ├── JwtConfigurer.java │ │ │ ├── JwtFilter.java │ │ │ ├── JwtItemController.java │ │ │ ├── JwtLog4jController.java │ │ │ ├── JwtTokenProvider.java │ │ │ └── JwtUserController.java │ │ ├── okta │ │ │ ├── OktaController.java │ │ │ └── OktaIdInfo.java │ │ └── token │ │ │ ├── TokenFilter.java │ │ │ └── TokenItemController.java │ │ ├── aspect │ │ └── UserServiceAspect.java │ │ ├── context │ │ └── TenantContext.java │ │ ├── controller │ │ ├── AdminController.java │ │ ├── HiddenController.java │ │ ├── IndexController.java │ │ ├── LinksController.java │ │ ├── LoginController.java │ │ ├── PayloadController.java │ │ └── SearchController.java │ │ ├── domain │ │ └── Hotel.java │ │ ├── entity │ │ ├── Item.java │ │ ├── TenantSupport.java │ │ └── User.java │ │ ├── form │ │ └── Search.java │ │ ├── hotel │ │ ├── dao │ │ │ └── HotelRepository.java │ │ ├── domain │ │ │ ├── Building.java │ │ │ ├── Continent.java │ │ │ ├── Hotel.java │ │ │ ├── RestErrorInfo.java │ │ │ └── Staff.java │ │ ├── exception │ │ │ ├── DataFormatException.java │ │ │ └── ResourceNotFoundException.java │ │ ├── rest │ │ │ ├── AbstractRestHandler.java │ │ │ └── HotelController.java │ │ └── service │ │ │ └── HotelService.java │ │ ├── interceptor │ │ └── DBInterceptor.java │ │ ├── repos │ │ ├── ItemRepo.java │ │ ├── ItemsRepo.java │ │ └── UserRepo.java │ │ └── service │ │ ├── SearchService.java │ │ ├── UserSearchService.java │ │ └── UserService.java │ └── resources │ ├── application-postgresql.yaml │ ├── application-windows.yaml │ ├── application.yaml │ ├── javavulny.crt │ ├── javavulny.p12 │ ├── keyStore.pem │ ├── keystore.p12 │ ├── log4j.properties │ └── templates │ ├── admin.html │ ├── basic-auth.html │ ├── companies.html │ ├── general.html │ ├── hidden.html │ ├── hidden2.html │ ├── index.html │ ├── jwt-auth.html │ ├── links.html │ ├── login-form-multi.html │ ├── login.html │ ├── payload-view.html │ ├── payloads.html │ ├── search.html │ ├── token-auth.html │ ├── user-search.html │ └── users.html ├── stackhawk-actions.yml ├── stackhawk.d ├── stackhawk-ajax.yml ├── stackhawk-auth-basic.yml ├── stackhawk-auth-external-jwt.yml ├── stackhawk-auth-external-token.yml ├── stackhawk-auth-form-cookie.yml ├── stackhawk-auth-json-token.yml ├── stackhawk-auth-script-form-multi.yml ├── stackhawk-custom-spider-curl.yml ├── stackhawk-custom-spider-newman.yml ├── stackhawk-fuzzer.yaml ├── stackhawk-github-pr.yml ├── stackhawk-har.yml ├── stackhawk-jsv-form-cookie.yml ├── stackhawk-jsv-json-token.yml ├── stackhawk-multi-cookie-auth.yml ├── stackhawk-okta.yml ├── stackhawk-openapi.yml └── stackhawk.yml ├── stackhawk.yml └── tools └── update-openapi-files.sh /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: 3 | pull_request: 4 | branches: 5 | - main 6 | 7 | jobs: 8 | 9 | build: 10 | runs-on: ubuntu-22.04 11 | steps: 12 | - uses: actions/checkout@v2 13 | - name: Set up JDK 17 14 | uses: actions/setup-java@v2 15 | with: 16 | distribution: 'temurin' 17 | java-version: '17' 18 | - name: Gradle build 19 | run: ./gradlew :build --stacktrace --info --rerun 20 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | name: CodeQL 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: [ "main" ] 7 | pull_request: 8 | # The branches below must be a subset of the branches above 9 | branches: [ "main" ] 10 | schedule: 11 | - cron: '23 17 * * 6' 12 | 13 | jobs: 14 | analyze: 15 | name: Analyze 16 | runs-on: ubuntu-latest 17 | permissions: 18 | actions: read 19 | contents: read 20 | security-events: write 21 | 22 | strategy: 23 | fail-fast: false 24 | matrix: 25 | language: [ 'java' ] 26 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 27 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 28 | 29 | steps: 30 | - name: Checkout repository 31 | uses: actions/checkout@v4 32 | 33 | # Initializes the CodeQL tools for scanning. 34 | - name: Initialize CodeQL 35 | uses: github/codeql-action/init@v3 36 | with: 37 | languages: ${{ matrix.language }} 38 | build-mode: 'manual' 39 | # If you wish to specify custom queries, you can do so here or in a config file. 40 | # By default, queries listed here will override any specified in a config file. 41 | # Prefix the list here with "+" to use these queries and those in the config file. 42 | 43 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 44 | # queries: security-extended,security-and-quality 45 | 46 | 47 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 48 | # If this step fails, then you should remove it and run the build manually (see below) 49 | #- name: Autobuild 50 | # uses: github/codeql-action/autobuild@v2 51 | 52 | # ℹ️ Command-line programs to run using the OS shell. 53 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 54 | 55 | # If the Autobuild fails above, remove it and uncomment the following three lines. 56 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 57 | - name: Set up JDK 17 58 | uses: actions/setup-java@v2 59 | with: 60 | distribution: 'temurin' 61 | java-version: '17' 62 | - name: Gradle build 63 | run: ./gradlew :build --stacktrace --info --rerun 64 | 65 | - name: Perform CodeQL Analysis 66 | uses: github/codeql-action/analyze@v3 67 | -------------------------------------------------------------------------------- /.github/workflows/hawkscan-manual.yml: -------------------------------------------------------------------------------- 1 | name: HawkScan Example 2 | on: 3 | workflow_dispatch: 4 | pull_request: 5 | branches: [ "main" ] 6 | permissions: 7 | # required for all workflows 8 | security-events: write 9 | # required to fetch internal or private CodeQL packs 10 | packages: read 11 | # only required for workflows in private repositories 12 | actions: read 13 | contents: read 14 | 15 | jobs: 16 | hawkscan: 17 | name: HawkScan 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/checkout@v4 21 | - name: Build and run JavaSpringVulny (slower) 22 | run: docker compose build && docker compose up -d 23 | - name: Run HawkScan 24 | id: run-hawkscan 25 | uses: stackhawk/hawkscan-action@main 26 | with: 27 | apiKey: ${{ secrets.HAWK_API_KEY }} 28 | configurationFiles: stackhawk.d/stackhawk.yml stackhawk.d/stackhawk-jsv-json-token.yml stackhawk.d/stackhawk-github-pr.yml 29 | env: 30 | #APP_ID: ${{ secrets.appId }} 31 | NO_PROGRESS: true 32 | SARIF_ARTIFACT: true 33 | COMMIT_SHA: ${{ github.event.pull_request.head.sha }} 34 | BRANCH_NAME: ${{ github.head_ref }} 35 | - name: Upload SARIF file 36 | uses: github/codeql-action/upload-sarif@v3 37 | with: 38 | # Path to SARIF file relative to the root of the repository 39 | sarif_file: stackhawk.sarif 40 | # Optional category for the results 41 | # Used to differentiate multiple results for one commit 42 | category: StackHawk 43 | -------------------------------------------------------------------------------- /.github/workflows/hawkscan.yml: -------------------------------------------------------------------------------- 1 | ## Intended to be used as a payload from HawkScan. 2 | ## It's broken, don't "fix" it. 3 | name: HawkScan 4 | on: 5 | repository_dispatch: 6 | types: [integration-initial-test] 7 | workflow_dispatch: 8 | 9 | jobs: 10 | hawkscan: 11 | name: HawkScan 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | - name: Create ${{ github.event.client_payload.repoName }} app directory 16 | run: mkdir -p common/build/integrationTests/hawkscan-action/apps/${{ github.event.client_payload.repoName }} 17 | - name: Navigate to ${{ github.event.client_payload.repoName }} app directory 18 | run: cd common/build/integrationTests/hawkscan-action/apps/${{ github.event.client_payload.repoName }} 19 | - name: Checkout vulny repo 20 | run: git clone https://github.com/${{ github.event.client_payload.repoOrg }}/${{ github.event.client_payload.repoName }}.git 21 | - name: Run ${{ github.event.client_payload.repoName }} 22 | run: docker compose up -d 23 | - name: Navigate to repo root directory 24 | run: cd /home/runner/work/${{ github.event.client_payload.repoName }}/${{ github.event.client_payload.repoName }} 25 | - name: Run HawkScan 26 | id: run-hawkscan 27 | uses: stackhawk/hawkscan-action@main 28 | with: 29 | apiKey: ${{ secrets.HAWK_API_KEY }} 30 | configurationFiles: ${{ github.event.client_payload.configFile }} 31 | sourceURL: ${{ github.event.client_payload.hawkscanSourceUrl }} 32 | version: ${{ github.event.client_payload.hawkscanVersion }} 33 | verbose: ${{ github.event.client_payload.verbose }} 34 | env: 35 | # APPLICATION_ID: ${{ github.event.client_payload.appId }} 36 | APP_ID: ${{ github.event.client_payload.appId }} 37 | NO_PROGRESS: true 38 | SARIF_ARTIFACT: true 39 | - name: Upload SARIF file 40 | uses: github/codeql-action/upload-sarif@v3 41 | with: 42 | # Path to SARIF file relative to the root of the repository 43 | sarif_file: stackhawk.sarif 44 | # Optional category for the results 45 | # Used to differentiate multiple results for one commit 46 | category: StackHawk 47 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Release Tag 2 | on: 3 | push: 4 | tags: 5 | - '*' 6 | 7 | jobs: 8 | release: 9 | if: startsWith(github.ref, 'refs/tags/') 10 | runs-on: ubuntu-22.04 11 | steps: 12 | - uses: actions/checkout@v4 13 | - name: Set up JDK 17 14 | uses: actions/setup-java@v4 15 | with: 16 | distribution: 'temurin' 17 | java-version: '17' 18 | - name: Gradle assemble 19 | run: './gradlew :assemble' 20 | - uses: softprops/action-gh-release@v1 21 | name: release files 22 | with: 23 | files: | 24 | build/libs/java-spring-vuly-0.2.0.jar 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle/ 2 | .idea/ 3 | build/ 4 | db 5 | bin/ 6 | .classpath 7 | .project 8 | .settings/ 9 | .env 10 | .DS_Store 11 | *.iml 12 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:17-jdk-slim 2 | 3 | RUN mkdir /javavulny /app 4 | COPY . /javavulny/ 5 | RUN sed -i 's/localhost\:5432/db\:5432/' /javavulny/src/main/resources/application.yaml 6 | 7 | RUN cd /javavulny \ 8 | && ./gradlew --no-daemon :build \ 9 | && cp build/libs/java-spring-vuly-0.2.0.jar /app/ \ 10 | && rm -Rf build/ \ 11 | && cd / \ 12 | && rm -Rf /javavulny /root/.gradle/ 13 | 14 | WORKDIR /app 15 | 16 | ENV PWD=/app 17 | CMD ["java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "/app/java-spring-vuly-0.2.0.jar"] 18 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | # HawkScan Scanning in Azure Pipelines 2 | # This is a demonstration of running JavaSpringVulny (a web applications) in azure-pipelines (a cicd pipeline) with the StackHawk extension (DAST testing) 3 | # https://marketplace.visualstudio.com/items?itemName=StackHawk.stackhawk-extensions 4 | # https://github.com/kaakaww/javaspringvulny 5 | # https://aka.ms/yaml 6 | 7 | # matrix builds for different build systems 8 | # use `condition: eq(variables['imageName'], 'ubuntu-latest')` property to filter tasks for specific operating systems 9 | 10 | strategy: 11 | matrix: 12 | windows-msi: 13 | imageName: "windows-latest" 14 | installerType: "msi" 15 | windows-zip: 16 | imageName: "windows-latest" 17 | installerType: "zip" 18 | linux-zip: 19 | imageName: "ubuntu-latest" 20 | installerType: "zip" 21 | windows-auto: 22 | imageName: "windows-latest" 23 | installerType: "auto" 24 | linux-auto: 25 | imageName: "ubuntu-latest" 26 | installerType: "auto" 27 | 28 | pool: 29 | vmImage: $(imageName) 30 | 31 | trigger: none 32 | 33 | steps: 34 | - checkout: self 35 | 36 | - script: echo Azure Pipelines build for $(imageName)! 37 | displayName: "🦅 $(imageName)" 38 | 39 | # install the latest version of hawkscan 40 | - task: HawkScanInstall@1 41 | inputs: 42 | version: "4.0.3" 43 | installerType: "$(installerType)" 44 | condition: not(and(eq(variables['installerType'], 'auto'), eq(variables['imageName'], 'ubuntu-latest'))) 45 | 46 | - task: HawkScanInstall@1 47 | inputs: 48 | version: "4.0.3" 49 | installerType: "$(installerType)" 50 | installPath: '/home/vsts/custom' 51 | condition: and(eq(variables['installerType'], 'auto'), eq(variables['imageName'], 'ubuntu-latest')) 52 | 53 | # azure pipelines default jdk is 8, so we upgrade to 11 to run JavaSpringVulny 54 | # the hawkscan msi bundles java with it, so this step isn't necesarry for running HawkScan 55 | - task: JavaToolInstaller@0 56 | inputs: 57 | versionSpec: "17" 58 | jdkArchitectureOption: "x64" 59 | jdkSourceOption: "PreInstalled" 60 | 61 | # download, then start javaspringVulny in the background 62 | - script: | 63 | curl -Ls https://github.com/kaakaww/javaspringvulny/releases/download/0.2.0/java-spring-vuly-0.2.0.jar -o ./java-spring-vuly-0.2.0.jar 64 | java -jar ./java-spring-vuly-0.2.0.jar & 65 | displayName: Start JavaSpringVulny on linux 66 | condition: eq(variables['imageName'], 'ubuntu-latest') 67 | 68 | # download, then start javaspringVulny in the background 69 | - powershell: | 70 | Invoke-WebRequest -Uri "https://github.com/kaakaww/javaspringvulny/releases/download/0.2.0/java-spring-vuly-0.2.0.jar" -OutFile "java-spring-vuly-0.2.0.jar" 71 | java --version 72 | Start-Process java -ArgumentList "-jar","java-spring-vuly-0.2.0.jar","--spring.profiles.active=windows" 73 | displayName: Start JavaSpringVulny on windows with gradle in the background 74 | env: 75 | SPRING_DATASOURCE_URL: 'jdbc:h2:file:D:\\a\\1\\db\\vulny;DB_CLOSE_ON_EXIT=FALSE;AUTO_RECONNECT=TRUE' 76 | condition: eq(variables['imageName'], 'windows-latest') 77 | 78 | # run hawkscan with the StackHawk Azure Extension 79 | - task: RunHawkScan@1 80 | inputs: 81 | configFile: "stackhawk.yml" 82 | version: "4.0.3" 83 | env: 84 | HAWK_API_KEY: $(HAWK_API_KEY) # use variables in the azure devops ui to configure secrets and env vars 85 | APP_ENV: $(imageName) 86 | APP_ID: $(appId2) 87 | SARIF_ARTIFACT: true 88 | condition: not(and(eq(variables['installerType'], 'auto'), eq(variables['imageName'], 'ubuntu-latest'))) 89 | 90 | - task: RunHawkScan@1 91 | inputs: 92 | configFile: "stackhawk.yml" 93 | version: "4.0.3" 94 | installPath: '/home/vsts/custom' 95 | env: 96 | HAWK_API_KEY: $(HAWK_API_KEY) # use variables in the azure devops ui to configure secrets and env vars 97 | APP_ENV: $(imageName) 98 | APP_ID: $(appId2) 99 | SARIF_ARTIFACT: true 100 | condition: and(eq(variables['installerType'], 'auto'), eq(variables['imageName'], 'ubuntu-latest')) 101 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.springframework.boot.gradle.tasks.bundling.BootJar 2 | 3 | plugins { 4 | java 5 | idea 6 | distribution 7 | id("org.springframework.boot") version "2.7.18" 8 | } 9 | apply(plugin = "io.spring.dependency-management") 10 | 11 | repositories { 12 | mavenCentral() 13 | } 14 | 15 | java.sourceCompatibility = JavaVersion.VERSION_17 16 | java.targetCompatibility = JavaVersion.VERSION_17 17 | 18 | dependencies { 19 | 20 | implementation("org.springframework.boot:spring-boot-starter-web") 21 | compileOnly("org.springframework.boot:spring-boot-devtools") 22 | implementation("org.springframework.boot:spring-boot-starter-data-jpa") 23 | implementation("org.springframework.boot:spring-boot-starter-security") 24 | implementation("org.springframework.boot:spring-boot-starter-thymeleaf") 25 | implementation("org.thymeleaf.extras:thymeleaf-extras-springsecurity5") 26 | implementation("org.springframework.boot:spring-boot-actuator") 27 | implementation("com.h2database:h2") 28 | runtimeOnly("org.postgresql:postgresql") 29 | implementation("io.jsonwebtoken:jjwt-api:0.10.7") 30 | implementation("org.apache.logging.log4j:log4j-slf4j-impl:2.14.1") 31 | // leave for log4shell test never change... meant to be vulnerable 32 | implementation("org.apache.logging.log4j:log4j-core:2.11.2") 33 | implementation("org.apache.logging.log4j:log4j-api:2.11.2") 34 | 35 | implementation("org.springdoc:springdoc-openapi-ui:1.8.0") 36 | 37 | testCompileOnly("junit:junit") 38 | 39 | implementation("io.jsonwebtoken:jjwt-impl:0.10.7") 40 | implementation("io.jsonwebtoken:jjwt-jackson:0.10.7") 41 | implementation("io.resurface:resurfaceio-logger:2.2.0") 42 | implementation("org.apache.commons:commons-compress:1.27.1") 43 | 44 | compileOnly("org.projectlombok:lombok:1.18.10") 45 | annotationProcessor("org.projectlombok:lombok:1.18.22") 46 | } 47 | 48 | configurations.implementation { 49 | exclude(group = "org.springframework.boot", module = "spring-boot-starter-logging") 50 | } 51 | 52 | tasks.named("bootJar") { 53 | archiveBaseName.set("java-spring-vuly") 54 | archiveVersion.set("0.2.0") 55 | } 56 | -------------------------------------------------------------------------------- /ci-examples/azure-devops/azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | # HawkScan Scanning in Azure Pipelines 2 | 3 | pool: 4 | vmImage: ubuntu-latest 5 | 6 | trigger: none 7 | 8 | steps: 9 | - checkout: self 10 | 11 | # install the latest version of hawkscan 12 | - task: HawkScanInstall@1 13 | inputs: 14 | installerType: "auto" 15 | 16 | # azure pipelines default jdk is 8, so we upgrade to 17 to run JavaSpringVulny 17 | - task: JavaToolInstaller@0 18 | inputs: 19 | versionSpec: "17" 20 | jdkArchitectureOption: "x64" 21 | jdkSourceOption: "PreInstalled" 22 | 23 | # download, then start javaspringVulny in the background 24 | - script: | 25 | curl -Ls https://github.com/kaakaww/javaspringvulny/releases/download/0.2.0/java-spring-vuly-0.2.0.jar -o ./java-spring-vuly-0.2.0.jar 26 | java -jar ./java-spring-vuly-0.2.0.jar & 27 | displayName: Download and Start JavaSpringVulny 28 | 29 | # run hawkscan with the StackHawk Azure Extension 30 | - task: RunHawkScan@1 31 | inputs: 32 | configFile: "stackhawk.yml" 33 | env: 34 | APP_ENV: 'Azure Pipeline' 35 | # use variables in the azure devops ui to configure secrets and env vars 36 | # specifying HAWK_API_KEY and APP_ID here are redundant but they MUST be set in the azure devops variables 37 | HAWK_API_KEY: $(HAWK_API_KEY) 38 | APP_ID: $(APP_ID) 39 | SARIF_ARTIFACT: true 40 | 41 | - task: PublishBuildArtifacts@1 42 | inputs: 43 | pathToPublish: '$(System.DefaultWorkingDirectory)/stackhawk.sarif' 44 | artifactName: 'CodeAnalysisLogs' 45 | publishLocation: 'Container' 46 | -------------------------------------------------------------------------------- /ci-examples/github/hawkscan.yml: -------------------------------------------------------------------------------- 1 | name: HawkScan Example 2 | on: 3 | workflow_dispatch: 4 | 5 | jobs: 6 | hawkscan: 7 | name: HawkScan 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - name: Download and run JavaSpringVulny release jar 12 | run: | 13 | curl -Ls https://github.com/kaakaww/javaspringvulny/releases/download/0.2.0/java-spring-vuly-0.2.0.jar -o ./java-spring-vuly-0.2.0.jar 14 | java -jar ./java-spring-vuly-0.2.0.jar & 15 | # - name: Build and run JavaSpringVulny (slower) 16 | # run: | 17 | # ./gradlew build 18 | # ./gradlew bootRun & 19 | - name: Run HawkScan 20 | id: run-hawkscan 21 | uses: stackhawk/hawkscan-action@main 22 | with: 23 | apiKey: ${{ secrets.HAWK_API_KEY }} 24 | sourceURL: https://download.stackhawk.com/dev/hawk/cli 25 | version: 3.9.12 26 | env: 27 | APP_ID: ${{ secrets.appId }} 28 | SARIF_ARTIFACT: true 29 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.2" 2 | services: 3 | db: 4 | image: postgres 5 | ports: 6 | - "5432:5432" 7 | environment: 8 | - POSTGRES_DB=postgresql 9 | - POSTGRES_USER=postgresql 10 | - POSTGRES_PASSWORD=postgresql 11 | 12 | javavulny: 13 | build: . 14 | image: stackhawk/javavulny:latest 15 | container_name: javavulny 16 | environment: 17 | SPRING_PROFILES_ACTIVE: postgresql 18 | SPRING_DATASOURCE_URL: jdbc:postgresql://db:5432/postgres 19 | ports: 20 | - "9000:9000" 21 | links: 22 | - db 23 | depends_on: 24 | - db 25 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaakaww/javaspringvulny/b50a7ae704f1f9c431adb74d3e7e46c7c7b4e2a2/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.4-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /hawkscripts/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Hawkscripts 3 | 4 | A directory of kotlin scripts to customize [HawkScan](https://docs.stackhawk.com/stackhawk-cli/#install-with-zip-file) with 5 | [IntelliJ IDE](https://www.jetbrains.com/idea/download) support via gradle. 6 | 7 | To enable IDE support... 8 | 9 | 1. Open a shell in the root of the javaspringvulny repository and run... 10 | ```shell 11 | ./gradlew :hawkscripts:download 12 | ``` 13 | This will download the hawk scripts sdk zip into the `hawkscripts/build` directory as 14 | required by the dependencies defined in [hawkscripts.gradle.kts](hawkscripts.gradle.kts). 15 | 1. Start the [IntelliJ IDE](https://www.jetbrains.com/idea/download) 16 | 1. Open javaspringvulny as a new gradle project 17 | 18 | ![intellij-new-project-1.png](help-images%2Fintellij-new-project-1.png) 19 | 20 | ![intellij-new-project-2.png](help-images%2Fintellij-new-project-2.png) 21 | 22 | ![intellij-new-project-3.png](help-images%2Fintellij-new-project-3.png) 23 | 24 | 1. **Wait for the indexer!** 25 | ![intellij-new-project-4.png](help-images%2Fintellij-new-project-4.png) 26 | 27 | When indexing is complete open any of the `.kts` files in the 28 | defined source directories `authentication, session, httpsender, active, proxy` 29 | to see activated code highlighting, auto-completion, and inline compilation errors. 30 | 31 | Use the [hawk perch]() and [hawk validate auth --watch]() to 32 | develop and test authentication and session scripts against your 33 | running web API's. -------------------------------------------------------------------------------- /hawkscripts/active/fuzzer.kts: -------------------------------------------------------------------------------- 1 | import com.github.javafaker.Faker 2 | import com.stackhawk.hste.extension.script.ScriptVars 3 | import com.stackhawk.hste.extension.scripts.scanrules.ScriptsActiveScanner 4 | import org.apache.log4j.LogManager 5 | import org.parosproxy.paros.network.HttpMessage 6 | 7 | val logger = LogManager.getLogger("fuzzer") 8 | 9 | val faker = Faker() 10 | val scriptVars = ScriptVars.getScriptVars("fuzzer.kts") 11 | 12 | fun alert(activeScanner: ScriptsActiveScanner, msg: HttpMessage, evidence: String, param: String, fuzzedParam: String) { 13 | val risk = 2 // 0: info, 1: low, 2: medium, 3: high 14 | val confidence = 3 // 0: falsePositive, 1: low, 2: medium, 3: high, 4: confirmed 15 | val title = "Fuzzer found a 5xx error" 16 | val description = "Fuzzer was able to find a 5xx error" 17 | val solution = "Handle bad input and never throw a 5xx error" 18 | val reference = "" 19 | val otherInfo = "fuzzed param: $param=$fuzzedParam" 20 | val pluginId = 1000000; //Custom Plugin ID 21 | 22 | activeScanner.newAlert() 23 | .setPluginId(pluginId) 24 | .setRisk(risk) 25 | .setConfidence(confidence) 26 | .setName(title) 27 | .setDescription(description) 28 | .setEvidence(evidence) 29 | .setOtherInfo(otherInfo) 30 | .setSolution(solution) 31 | .setReference(reference) 32 | .setMessage(msg) 33 | .raise(); 34 | } 35 | 36 | fun scanNode(activeScanner: ScriptsActiveScanner, origMessage: HttpMessage) { 37 | logger.debug("scanNode fuzzer hook: ${origMessage.requestHeader.uri}") 38 | return 39 | } 40 | 41 | fun scan(activeScanner: ScriptsActiveScanner, origMessage: HttpMessage, param: String, value: String) { 42 | logger.debug("scan fuzzer hook: ${origMessage.requestHeader.uri} | ${param}=${value}") 43 | val iterations = scriptVars["iterations"]?.toInt() ?: 1 44 | val stringStartLength = scriptVars["stringStartLength"]?.toInt() ?: 1 45 | val stringEndLength = scriptVars["stringEndLength"]?.toInt() ?: 100 46 | (1..iterations).forEach { i -> 47 | val msg = origMessage.cloneRequest() 48 | val fuzzedParamValue = if (i % 2 == 0) { 49 | faker.lorem().characters(stringStartLength, stringEndLength) 50 | } else { 51 | faker.harryPotter().spell() 52 | } 53 | 54 | if (param.isNotBlank()) { 55 | activeScanner.setParam(msg, param, fuzzedParamValue) 56 | } 57 | try { 58 | activeScanner.sendAndReceive(msg, false, false) 59 | if (msg.responseHeader.statusCode >= 500) { 60 | logger.debug("request: ${msg.requestHeader}${msg.requestBody}") 61 | alert(activeScanner, msg, msg.responseHeader.primeHeader, param, fuzzedParamValue) 62 | logger.debug("response: ${msg.responseHeader.statusCode} ${msg.responseHeader}${msg.responseBody}") 63 | } 64 | } catch (e: Exception) { 65 | logger.error("Error sending request: ${e.message}") 66 | } 67 | } 68 | 69 | } -------------------------------------------------------------------------------- /hawkscripts/authentication/form-auth-multi.kts: -------------------------------------------------------------------------------- 1 | import com.fasterxml.jackson.databind.ObjectMapper 2 | import com.stackhawk.hste.extension.talon.HawkConfExtensions 3 | import com.stackhawk.hste.extension.talon.cleanHost 4 | import com.stackhawk.hste.extension.talon.hawkscan.ExtensionTalonHawkscan 5 | import org.apache.commons.httpclient.URI 6 | import org.apache.log4j.LogManager 7 | import org.parosproxy.paros.control.Control 8 | import org.parosproxy.paros.network.HttpHeader 9 | import org.parosproxy.paros.network.HttpMessage 10 | import org.parosproxy.paros.network.HttpRequestHeader 11 | import com.stackhawk.hste.authentication.AuthenticationHelper 12 | import com.stackhawk.hste.authentication.GenericAuthenticationCredentials 13 | 14 | val logger = LogManager.getLogger("form-auth-multi") 15 | 16 | val talon = Control 17 | .getSingleton() 18 | .extensionLoader 19 | .getExtension(ExtensionTalonHawkscan::class.java) 20 | 21 | fun hostUrl(path: String): String { 22 | return "${HawkConfExtensions.cleanHost(talon.talonHawkScanConf.hawkscanConf.app)}$path" 23 | } 24 | 25 | // This function is called before a scan is started and when the loggedOutIndicator is matched indicating re-authentication is needed. 26 | fun authenticate( 27 | helper: AuthenticationHelper, 28 | paramsValues: Map, 29 | credentials: GenericAuthenticationCredentials, 30 | ): HttpMessage { 31 | logger.info("Kotlin auth template") 32 | logger.info("TalonConf: ${talon.talonHawkScanConf}") 33 | logger.info("host ${talon.talonHawkScanConf.hawkscanConf.app.cleanHost()}") 34 | 35 | val mapper = ObjectMapper() 36 | val payload = mapper.writeValueAsString( 37 | mapOf( 38 | "username" to credentials.getParam("username"), 39 | "password" to credentials.getParam("password"), 40 | ), 41 | ) 42 | 43 | logger.info("payload? $payload") 44 | 45 | val loginPagePathUrl = hostUrl(paramsValues["loginPagePath"]!!) 46 | logger.info("TARGET_URL: $loginPagePathUrl") 47 | val msg = helper.prepareMessage() 48 | msg.requestHeader = HttpRequestHeader( 49 | HttpRequestHeader.GET, 50 | URI(loginPagePathUrl, true), 51 | HttpHeader.HTTP11, 52 | ) 53 | logger.info("msg: ${msg.requestHeader} ${msg.requestBody} ${msg.requestHeader.headers.size}") 54 | msg.requestHeader.headers.forEach { println(it) } 55 | helper.sendAndReceive(msg) 56 | logger.info("resp: ${msg.responseHeader} ${msg.responseBody} ") 57 | 58 | if (msg.responseBody.length() > 0) { 59 | val map = mapper.readValue(msg.responseBody.bytes, Map::class.java) 60 | logger.info("map $map") 61 | } else { 62 | logger.info("no body to parse") 63 | } 64 | return msg 65 | } 66 | 67 | // The required parameter names for your script, your script will throw an error if these are not supplied in the script.parameters configuration. 68 | fun getRequiredParamsNames(): Array { 69 | return arrayOf("loginPagePath", "loginPage", "remember") 70 | } 71 | 72 | // The required credential parameters, your script will throw an error if these are not supplied in the script.credentials configuration. 73 | fun getCredentialsParamsNames(): Array { 74 | return arrayOf("username", "password") 75 | } 76 | 77 | fun getOptionalParamsNames(): Array { 78 | return arrayOf("logging", "formType", "csrfExtra") 79 | } 80 | 81 | fun getLoggedInIndicator(): String { 82 | return "" 83 | } 84 | 85 | fun getLoggedOutIndicator(): String { 86 | return "" 87 | } 88 | -------------------------------------------------------------------------------- /hawkscripts/authentication/okta-client-credentials-basic.kts: -------------------------------------------------------------------------------- 1 | import com.fasterxml.jackson.databind.ObjectMapper 2 | import com.fasterxml.jackson.databind.node.ObjectNode 3 | import java.util.Base64 4 | import java.util.TreeSet 5 | import org.apache.commons.httpclient.URI 6 | import org.apache.hc.client5.http.auth.AuthenticationException 7 | import org.apache.hc.core5.http.ContentType 8 | import org.apache.hc.core5.http.HttpHeaders 9 | import org.apache.hc.core5.http.Method 10 | import org.apache.log4j.LogManager 11 | import org.parosproxy.paros.network.HtmlParameter 12 | import org.parosproxy.paros.network.HttpHeader 13 | import org.parosproxy.paros.network.HttpMessage 14 | import org.parosproxy.paros.network.HttpRequestHeader 15 | import com.stackhawk.hste.authentication.AuthenticationHelper 16 | import com.stackhawk.hste.authentication.GenericAuthenticationCredentials 17 | 18 | val logger = LogManager.getLogger("okta-auth") 19 | val mapper = ObjectMapper() 20 | 21 | fun authenticate( 22 | helper: AuthenticationHelper, 23 | paramsValues: Map, 24 | credentials: GenericAuthenticationCredentials, 25 | ): HttpMessage { 26 | logger.info("auth hook") 27 | 28 | val oktaDomain = paramsValues["okta_domain"] 29 | val scope = paramsValues["scope"] 30 | val clientId = credentials.getParam("client_id") 31 | val clientSecret = credentials.getParam("client_secret") 32 | val base64Creds = Base64.getEncoder().encodeToString("$clientId:$clientSecret".toByteArray()) 33 | 34 | 35 | val msg = helper.prepareMessage() 36 | msg.requestHeader = HttpRequestHeader( 37 | Method.POST.name, 38 | URI("https://$oktaDomain/oauth2/default/v1/token", true), 39 | HttpHeader.HTTP11 40 | ) 41 | msg.requestHeader.addHeader(HttpHeaders.ACCEPT, ContentType.APPLICATION_JSON.toString()) 42 | msg.requestHeader.addHeader(HttpHeaders.AUTHORIZATION, "Basic $base64Creds") 43 | 44 | val formTree = TreeSet() 45 | formTree.add(HtmlParameter(HtmlParameter.Type.form, "grant_type", "client_credentials")) 46 | formTree.add(HtmlParameter(HtmlParameter.Type.form, "scope", scope)) 47 | msg.requestBody.setFormParams(formTree) 48 | msg.requestHeader.contentLength = msg.requestBody.length() 49 | 50 | logger.info("::::::auth request:::::\n${msg.requestHeader}${msg.requestBody}") 51 | 52 | helper.sendAndReceive(msg) 53 | 54 | logger.info("::::::auth response:::::\n${msg.responseHeader}${msg.responseBody}") 55 | 56 | // Throw an authentication exception if the status code is not 2xx 57 | if (!(200..299).contains(msg.responseHeader.statusCode)) { 58 | val jsonObject = mapper.readValue(msg.responseBody.bytes, ObjectNode::class.java) 59 | val err = jsonObject.get("error").asText() 60 | val errDesc = jsonObject.get("error_description").asText() 61 | throw AuthenticationException("$err $errDesc") 62 | } 63 | 64 | return msg 65 | } 66 | 67 | fun getRequiredParamsNames(): Array { 68 | return arrayOf("okta_domain", "scope") 69 | } 70 | 71 | fun getCredentialsParamsNames(): Array { 72 | return arrayOf("client_id", "client_secret") 73 | } 74 | 75 | fun getOptionalParamsNames(): Array { 76 | return arrayOf() 77 | } 78 | 79 | fun getLoggedInIndicator(): String { 80 | return "" 81 | } 82 | 83 | fun getLoggedOutIndicator(): String { 84 | return "" 85 | } 86 | -------------------------------------------------------------------------------- /hawkscripts/hawkscripts.gradle.kts: -------------------------------------------------------------------------------- 1 | import java.net.HttpURLConnection 2 | import java.net.URL 3 | import java.nio.file.Files 4 | import kotlin.math.roundToLong 5 | 6 | plugins { 7 | kotlin("jvm") version "1.8.22" 8 | } 9 | 10 | val kotlinVersion = "1.8.22" 11 | val hawkScriptSdkVersion = lazy { sdkVersion() } 12 | val sdkZipName = lazy { "hawkscript-sdk-${hawkScriptSdkVersion.value}.zip" } 13 | val hawkScriptSDKZip = lazy { "$buildDir/${sdkZipName.value}" } 14 | 15 | tasks.compileKotlin.configure { 16 | if (!File(hawkScriptSDKZip.value).exists()) { 17 | logger.warn("hawkscripts sdk zip ${hawkScriptSDKZip.value} not found") 18 | logger.lifecycle("Run ./gradlew :hawkscripts:download to enable kotlin scripting support for IntelliJ") 19 | } 20 | enabled = File(hawkScriptSDKZip.value).exists() 21 | } 22 | 23 | kotlin { 24 | sourceSets { 25 | main { 26 | kotlin { 27 | srcDirs( 28 | "authentication", 29 | "session", 30 | "httpsender", 31 | "active", 32 | "proxy", 33 | ) 34 | } 35 | } 36 | } 37 | } 38 | 39 | repositories { 40 | mavenCentral() 41 | } 42 | 43 | dependencies { 44 | compileOnly("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion") 45 | compileOnly("org.jetbrains.kotlin:kotlin-script-runtime:$kotlinVersion") 46 | compileOnly(zipTree(hawkScriptSDKZip.value)) 47 | } 48 | tasks.register("download") { 49 | 50 | group = "StackHawk" 51 | description = "Download the latest hawk scripting sdk zip" 52 | 53 | doLast { 54 | 55 | Files.createDirectories(buildDir.toPath()) 56 | val localSdkZip = File("$buildDir/${sdkZipName.value}") 57 | if (!localSdkZip.exists()) { 58 | val hawkscriptSdkUrl = URL("https://download.stackhawk.com/hawk/sdk/${sdkZipName.value}") 59 | val zipConn = hawkscriptSdkUrl.openConnection() as HttpURLConnection 60 | zipConn.connect() 61 | if ((200..299).contains(zipConn.responseCode)) { 62 | logger.lifecycle("Downloading $hawkscriptSdkUrl -> $localSdkZip") 63 | val delay = 1000L 64 | var curLen = 0 65 | val len = zipConn.getHeaderField("Content-Length").toLong() 66 | val input = zipConn.inputStream 67 | localSdkZip.outputStream().use { output -> 68 | var buf = ByteArray(8192) 69 | var c = input.read(buf, 0, buf.size) 70 | var lastPct = 0L 71 | while (c != -1) { 72 | curLen += c 73 | output.write(buf, 0, c) 74 | c = input.read(buf, 0, buf.size) 75 | val pc = ((curLen.toDouble() / len.toDouble()) * 100).roundToLong() 76 | if ((System.currentTimeMillis() % delay) == 0L && lastPct != pc) { 77 | logger.lifecycle("${sdkZipName.value} [${curLen / 1024 / 1024}mb] ${pc}%") 78 | lastPct = pc 79 | } 80 | } 81 | val pc = ((curLen.toDouble() / len.toDouble()) * 100).roundToLong() 82 | logger.lifecycle("${sdkZipName.value} [${curLen / 1024 / 1024}mb] ${pc}%") 83 | } 84 | } else { 85 | logger.error("Error downloading $hawkscriptSdkUrl ${zipConn.responseMessage}") 86 | } 87 | } else { 88 | logger.lifecycle("latest hawkscan sdk already found: ${"$buildDir/${sdkZipName.value}"}") 89 | } 90 | 91 | } 92 | } 93 | 94 | fun sdkVersion(): String { 95 | val verFile = File("$buildDir/hawkscriptsdk.version") 96 | val ret = if (verFile.exists()) { 97 | verFile.readText() 98 | } else { 99 | downloadSdkVersion() 100 | verFile.readText() 101 | } 102 | return ret 103 | } 104 | 105 | fun downloadSdkVersion() { 106 | Files.createDirectories(buildDir.toPath()) 107 | val hawkscanVersionUrl = URL("https://api.stackhawk.com/hawkscan/version") 108 | val verisionConn = hawkscanVersionUrl.openConnection() as HttpURLConnection 109 | verisionConn.connect() 110 | val version = String(verisionConn.inputStream.readAllBytes()) 111 | File("$buildDir/hawkscriptsdk.version").outputStream().use { 112 | it.write(version.toByteArray()) 113 | } 114 | } 115 | 116 | -------------------------------------------------------------------------------- /hawkscripts/help-images/intellij-new-project-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaakaww/javaspringvulny/b50a7ae704f1f9c431adb74d3e7e46c7c7b4e2a2/hawkscripts/help-images/intellij-new-project-1.png -------------------------------------------------------------------------------- /hawkscripts/help-images/intellij-new-project-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaakaww/javaspringvulny/b50a7ae704f1f9c431adb74d3e7e46c7c7b4e2a2/hawkscripts/help-images/intellij-new-project-2.png -------------------------------------------------------------------------------- /hawkscripts/help-images/intellij-new-project-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaakaww/javaspringvulny/b50a7ae704f1f9c431adb74d3e7e46c7c7b4e2a2/hawkscripts/help-images/intellij-new-project-3.png -------------------------------------------------------------------------------- /hawkscripts/help-images/intellij-new-project-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaakaww/javaspringvulny/b50a7ae704f1f9c431adb74d3e7e46c7c7b4e2a2/hawkscripts/help-images/intellij-new-project-4.png -------------------------------------------------------------------------------- /hawkscripts/httpsender/custom-sender.kts: -------------------------------------------------------------------------------- 1 | import com.stackhawk.hste.extension.talon.hawkscan.ExtensionTalonHawkscan 2 | import org.apache.log4j.LogManager 3 | import org.parosproxy.paros.control.Control 4 | import org.parosproxy.paros.network.HttpMessage 5 | import com.stackhawk.hste.extension.script.HttpSenderScriptHelper 6 | 7 | val logger = LogManager.getLogger("custom-http-sender") 8 | 9 | val talon = Control 10 | .getSingleton() 11 | .extensionLoader 12 | .getExtension(ExtensionTalonHawkscan::class.java) 13 | 14 | // modify a request before it's sent to the web application 15 | fun sendingRequest(msg: HttpMessage, initiator: Int, helper: HttpSenderScriptHelper) { 16 | logger.info("req ${msg.requestHeader.uri}") 17 | msg.requestHeader.setHeader("X-HawkScanId", talon.talonHawkScanConf.scanId) 18 | } 19 | 20 | // modify the response from the web application before sending to the client 21 | fun responseReceived(msg: HttpMessage, initiator: Int, helper: HttpSenderScriptHelper) { 22 | } 23 | -------------------------------------------------------------------------------- /hawkscripts/httpsender/log-http-payloads.kts: -------------------------------------------------------------------------------- 1 | 2 | import org.apache.log4j.LogManager 3 | import org.parosproxy.paros.network.HttpMessage 4 | import com.stackhawk.hste.extension.script.HttpSenderScriptHelper 5 | 6 | val logger = LogManager.getLogger("log-http-payloads") 7 | 8 | // modify a request before it's sent to the web application 9 | fun sendingRequest(msg: HttpMessage, initiator: Int, helper: HttpSenderScriptHelper) { 10 | 11 | } 12 | 13 | // modify the response from the web application before sending to the client 14 | fun responseReceived(msg: HttpMessage, initiator: Int, helper: HttpSenderScriptHelper) { 15 | val httpRequestAndResponse = msg.requestHeader.toString() + 16 | msg.requestBody.toString() + 17 | msg.responseHeader.toString() + 18 | msg.responseBody.toString() 19 | 20 | logger.info("request/response: $httpRequestAndResponse") 21 | } 22 | -------------------------------------------------------------------------------- /hawkscripts/session/jwt-session.kts: -------------------------------------------------------------------------------- 1 | import com.fasterxml.jackson.databind.ObjectMapper 2 | import com.fasterxml.jackson.databind.node.ObjectNode 3 | import com.nimbusds.jwt.JWTClaimsSet 4 | import com.nimbusds.jwt.SignedJWT 5 | import java.time.Instant 6 | import org.apache.log4j.LogManager 7 | import com.stackhawk.hste.session.ScriptBasedSessionManagementMethodType 8 | import com.stackhawk.hste.extension.script.ScriptVars 9 | 10 | val logger = LogManager.getLogger("okta-json-to-token") 11 | val mapper = ObjectMapper() 12 | 13 | fun extractWebSession(sessionWrapper: ScriptBasedSessionManagementMethodType.SessionWrapper) { 14 | 15 | val tokenField = sessionWrapper.getParam("jwt_token_field") 16 | val tokenType = sessionWrapper.getParam("token_type_field") ?: "Bearer" 17 | 18 | logger.info("get token from json: ${sessionWrapper.httpMessage.responseBody}") 19 | val jsonObject = mapper.readValue(sessionWrapper.httpMessage.responseBody.bytes, ObjectNode::class.java) 20 | val accessToken = jsonObject.get(tokenField).asText() 21 | ScriptVars.setGlobalVar("auth_header_value", "$tokenType $accessToken") 22 | 23 | sessionWrapper.session.setValue("jwt", accessToken) 24 | 25 | val jwt = SignedJWT.parse(accessToken) 26 | logger.info("jwt-expires: ${jwt.jwtClaimsSet.expirationTime}") 27 | sessionWrapper.session.setValue("jwt_claims", jwt.jwtClaimsSet) 28 | } 29 | 30 | fun processMessageToMatchSession(sessionWrapper: ScriptBasedSessionManagementMethodType.SessionWrapper) { 31 | 32 | val nowish = Instant.now().minusMillis(15000) 33 | val jwtClaims = sessionWrapper.session.getValue("jwt_claims") as JWTClaimsSet? 34 | val isExpired = jwtClaims?.expirationTime?.toInstant()?.isBefore(nowish) 35 | 36 | if (isExpired == true) { 37 | logger.info("session expires @ ${jwtClaims.expirationTime}") 38 | synchronized(this) { 39 | sessionWrapper.httpMessage.requestingUser.authenticate() 40 | } 41 | } 42 | 43 | logger.debug("session-jwt: ${sessionWrapper.session.getValue("jwt")}") 44 | 45 | val hdrVal = ScriptVars.getGlobalVar("auth_header_value") 46 | logger.debug("auth_header_value: $hdrVal") 47 | if (!hdrVal.isNullOrEmpty()) { 48 | sessionWrapper.httpMessage.requestHeader.setHeader("Authorization", hdrVal) 49 | } 50 | 51 | } 52 | 53 | fun clearWebSessionIdentifiers(sessionWrapper: ScriptBasedSessionManagementMethodType.SessionWrapper) { 54 | } 55 | 56 | fun getRequiredParamsNames(): Array { 57 | return arrayOf("jwt_token_field") 58 | } 59 | 60 | fun getOptionalParamsNames(): Array { 61 | return arrayOf("token_type_field") 62 | } -------------------------------------------------------------------------------- /integration_tests/conf_files/stackhawk-jsv-json-token.yml: -------------------------------------------------------------------------------- 1 | hawk: 2 | spider: 3 | maxDurationMinutes: 2 4 | app: 5 | applicationId: ${APPLICATION_ID:test-app-id} 6 | env: ${ENV:dev} 7 | contactEmail: hawkdeploy@stackhawk.com 8 | host: ${APP_HOST:https://localhost:9000} 9 | openApiConf: 10 | filePath: openapi.yaml 11 | authentication: 12 | loggedInIndicator: "\\QSign Out\\E" 13 | loggedOutIndicator: ".*Location:.*/login.*" 14 | usernamePassword: 15 | type: JSON 16 | loginPath: /api/jwt/auth/signin 17 | usernameField: username 18 | passwordField: password 19 | scanUsername: "user" 20 | scanPassword: "password" 21 | tokenAuthorization: 22 | type: HEADER 23 | value: Authorization 24 | tokenType: Bearer 25 | tokenExtraction: 26 | type: TOKEN_PATH 27 | value: "token" 28 | testPath: 29 | path: /api/jwt/items/search/i 30 | success: ".*200.*" 31 | autoPolicy: true 32 | autoInputVectors: true -------------------------------------------------------------------------------- /local-hawk.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | docker run --rm -e API_KEY=$LOCAL_HAWK_API_KEY -e SHAWK_RESULTS_ENDPOINT=http://host.docker.internal:6300/api/v1 -e SHAWK_AUTH_ENDPOINT=http://host.docker.internal:6200 -v $(pwd):/hawk:rw -it stackhawk/hawkscan:latest 4 | 5 | #docker run --rm -e API_KEY=$SANDBOX_HAWK_API_KEY -v $(pwd):/hawk:rw -it stackhawk/hawkscan:latest -------------------------------------------------------------------------------- /scan-rules.conf: -------------------------------------------------------------------------------- 1 | # zap-full-scan rule configuration file 2 | # Change WARN to IGNORE to ignore rule or FAIL to fail if rule matches 3 | # Active scan rules set to IGNORE will not be run which will speed up the scan 4 | # Only the rule identifiers are used - the names are just for info 5 | # You can add your own messages to each rule by appending them after a tab on each line. 6 | 0 WARN (Directory Browsing - Active/release) 7 | 10010 WARN (Cookie No HttpOnly Flag - Passive/release) 8 | 10011 WARN (Cookie Without Secure Flag - Passive/release) 9 | 10015 WARN (Incomplete or No Cache-control and Pragma HTTP Header Set - Passive/release) 10 | 10016 FAIL (Web Browser XSS Protection Not Enabled - Passive/release) 11 | 10017 WARN (Cross-Domain JavaScript Source File Inclusion - Passive/release) 12 | 10019 WARN (Content-Type Header Missing - Passive/release) 13 | 10020 WARN (X-Frame-Options Header Scanner - Passive/release) 14 | 10021 WARN (X-Content-Type-Options Header Missing - Passive/release) 15 | 10023 WARN (Information Disclosure - Debug Error Messages - Passive/release) 16 | 10024 WARN (Information Disclosure - Sensitive Information in URL - Passive/beta) 17 | 10025 WARN (Information Disclosure - Sensitive Information in HTTP Referrer Header - Passive/beta) 18 | 10026 WARN (HTTP Parameter Override - Passive/beta) 19 | 10027 WARN (Information Disclosure - Suspicious Comments - Passive/beta) 20 | 10032 WARN (Viewstate Scanner - Passive/release) 21 | 10037 WARN (Server Leaks Information via "X-Powered-By" HTTP Response Header Field(s) - Passive/beta) 22 | 10040 WARN (Secure Pages Include Mixed Content - Passive/release) 23 | 10045 WARN (Source Code Disclosure - /WEB-INF folder - Active/release) 24 | 10048 WARN (Remote Code Execution - Shell Shock - Active/beta) 25 | 10054 WARN (Cookie Without SameSite Attribute - Passive/beta) 26 | 10055 WARN (CSP Scanner - Passive/release) 27 | 10056 WARN (X-Debug-Token Information Leak - Passive/beta) 28 | 10057 WARN (Username Hash Found - Passive/beta) 29 | 10061 WARN (X-AspNet-Version Response Header Scanner - Passive/beta) 30 | 10095 WARN (Backup File Disclosure - Active/beta) 31 | 10096 WARN (Timestamp Disclosure - Passive/beta) 32 | 10098 WARN (Cross-Domain Misconfiguration - Passive/beta) 33 | 10105 WARN (Weak Authentication Method - Passive/release) 34 | 10202 WARN (Absence of Anti-CSRF Tokens - Passive/release) 35 | 2 WARN (Private IP Disclosure - Passive/release) 36 | 20012 WARN (Anti CSRF Tokens Scanner - Active/beta) 37 | 20014 WARN (HTTP Parameter Pollution scanner - Active/beta) 38 | 20015 WARN (Heartbleed OpenSSL Vulnerability - Active/beta) 39 | 20016 WARN (Cross-Domain Misconfiguration - Active/beta) 40 | 20017 WARN (Source Code Disclosure - CVE-2012-1823 - Active/beta) 41 | 20018 WARN (Remote Code Execution - CVE-2012-1823 - Active/beta) 42 | 20019 WARN (External Redirect - Active/release) 43 | 3 WARN (Session ID in URL Rewrite - Passive/release) 44 | 30001 WARN (Buffer Overflow - Active/release) 45 | 30002 WARN (Format String Error - Active/release) 46 | 30003 WARN (Integer Overflow Error - Active/beta) 47 | 40003 WARN (CRLF Injection - Active/release) 48 | 40008 WARN (Parameter Tampering - Active/release) 49 | 40009 WARN (Server Side Include - Active/release) 50 | 40012 FAIL (Cross Site Scripting (Reflected) - Active/release) 51 | 40013 WARN (Session Fixation - Active/beta) 52 | 40014 FAIL (Cross Site Scripting (Persistent) - Active/release) 53 | 40016 FAIL (Cross Site Scripting (Persistent) - Prime - Active/release) 54 | 40017 FAIL (Cross Site Scripting (Persistent) - Spider - Active/release) 55 | 40018 FAIL (SQL Injection - Active/release) 56 | 40019 FAIL (SQL Injection - MySQL - Active/beta) 57 | 40020 FAIL (SQL Injection - Hypersonic SQL - Active/beta) 58 | 40021 FAIL (SQL Injection - Oracle - Active/beta) 59 | 40022 FAIL (SQL Injection - PostgreSQL - Active/beta) 60 | 40023 WARN (Possible Username Enumeration - Active/beta) 61 | 42 WARN (Source Code Disclosure - SVN - Active/beta) 62 | 50000 WARN (Script Active Scan Rules - Active/release) 63 | 50001 WARN (Script Passive Scan Rules - Passive/release) 64 | 6 WARN (Path Traversal - Active/release) 65 | 7 WARN (Remote File Inclusion - Active/release) 66 | 90001 WARN (Insecure JSF ViewState - Passive/release) 67 | 90011 WARN (Charset Mismatch - Passive/release) 68 | 90019 WARN (Server Side Code Injection - Active/release) 69 | 90020 WARN (Remote OS Command Injection - Active/release) 70 | 90021 WARN (XPath Injection - Active/beta) 71 | 90022 WARN (Application Error Disclosure - Passive/release) 72 | 90023 WARN (XML External Entity Attack - Active/beta) 73 | 90024 WARN (Generic Padding Oracle - Active/beta) 74 | 90025 WARN (Expression Language Injection - Active/beta) 75 | 90028 WARN (Insecure HTTP Method - Active/beta) 76 | 90033 WARN (Loosely Scoped Cookie - Passive/release) 77 | -------------------------------------------------------------------------------- /scripts/basic-auth.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Base64 encode the username=user and password=password combo 4 | echo -n "user:password" | base64 5 | -------------------------------------------------------------------------------- /scripts/json-auth.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Fetch a JWT token from JavaSpringVulny using the JSON signin endpoint 4 | RESPONSE=$( curl --request POST 'https://localhost:9000/api/jwt/auth/signin' \ 5 | --header 'Content-Type: application/json' \ 6 | --data-raw '{"password": "password", "username": "user"}' \ 7 | --silent --insecure ) 8 | 9 | # Extract the JWT token from the JSON response 10 | echo $RESPONSE | jq '.token' -r 11 | -------------------------------------------------------------------------------- /scripts/multi-cookie-auth.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Request login (XLOGINID) and session (JSESSIONID) cookies from server 4 | curl -k -c cookie-jar.txt https://localhost:9000/login-code 5 | # Set local JSESSIONID variable to the JSESSIONID cookie 6 | JSESSIONID=$(awk 'match($0, /JSESSIONID.*/){print substr($0, RSTART + 11, RLENGTH)}' cookie-jar.txt ) 7 | # Set local XLOGINID variable to the XLOGINID cookie 8 | XLOGINID=$(awk 'match($0, /XLOGINID.*/){print substr($0, RSTART + 9, RLENGTH)}' cookie-jar.txt) 9 | # Request page with XLOGINID and JSESSIONID cookies and extract the _csrf token 10 | CSRF=$(curl -k -b cookie-jar.txt \ 11 | https://localhost:9000/login-form-multi | awk 'match($0,/_csrf".*/) { print substr($0, RSTART+14, RLENGTH -17)}') 12 | # Log into the mutli cooke endpoint using XLOGINID and JSESSIONID cookies and username/password 13 | curl -v -k \ 14 | -d "_csrf=${CSRF}&loginCode=${XLOGINID}&username=user&password=password&remember=on" \ 15 | -b cookie-jar.txt \ 16 | -H "Content-Type: application/x-www-form-urlencoded" \ 17 | "https://localhost:9000/login-form-multi" 18 | 19 | # Run HawkScan injecting local variables as environment variables 20 | hawk scan -e JSESSIONID=${JSESSIONID} -e XLOGINID=${XLOGINID} ./stackhawk.d/stackhawk-multi-cookie-auth.yml 21 | 22 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "JavaSpringVulny" 2 | include("hawkscripts") 3 | 4 | rootProject.children.forEach { projectDescriptor -> 5 | projectDescriptor.buildFileName = "${projectDescriptor.name}.gradle.kts" 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/hawk/Application.java: -------------------------------------------------------------------------------- 1 | package hawk; 2 | 3 | import java.util.Arrays; 4 | import java.util.stream.Stream; 5 | 6 | import hawk.context.TenantContext; 7 | import hawk.entity.Item; 8 | import hawk.entity.User; 9 | import hawk.repos.ItemRepo; 10 | import hawk.repos.UserRepo; 11 | import org.springframework.beans.factory.annotation.Value; 12 | import org.springframework.boot.CommandLineRunner; 13 | import org.springframework.boot.SpringApplication; 14 | import org.springframework.boot.autoconfigure.SpringBootApplication; 15 | import org.springframework.context.ApplicationContext; 16 | import org.springframework.context.annotation.Bean; 17 | 18 | @SpringBootApplication 19 | public class Application { 20 | 21 | public static void main(String[] args) { 22 | SpringApplication.run(Application.class, args); 23 | } 24 | 25 | @Value("${spring.datasource.url}") 26 | private String dbUrl; 27 | 28 | @Bean 29 | public CommandLineRunner commandLineRunner(ApplicationContext ctx, ItemRepo repo, UserRepo userRepo) { 30 | 31 | 32 | return args -> { 33 | 34 | System.out.println("Let's inspect the beans provided by Spring Boot:"); 35 | 36 | String[] beanNames = ctx.getBeanDefinitionNames(); 37 | Arrays.sort(beanNames); 38 | for (String beanName : beanNames) { 39 | System.out.println(beanName); 40 | } 41 | 42 | 43 | System.out.println(String.format("Load some fixture data %s", dbUrl)); 44 | 45 | System.out.println(String.format("Items in DB %d", repo.count())); 46 | 47 | if (repo.count() == 0) { 48 | repo.findAll().forEach(item -> System.out.println(String.format("item: %s", item.getName()))); 49 | 50 | Stream.of(1, 2, 3).forEach(i -> { 51 | System.out.println(String.format("Adding item%d", i)); 52 | repo.save(new Item(String.format("item%d", i), String.format("we have the best items, item%d", i))); 53 | }); 54 | 55 | System.out.println(String.format("Items in DB %d", repo.count())); 56 | repo.findAll().forEach(item -> System.out.println(String.format("item: %s", item.getName()))); 57 | } 58 | 59 | System.out.println(String.format("Users in DB %d", userRepo.count())); 60 | 61 | if (userRepo.count() == 0) { 62 | userRepo.findAll().forEach(item -> System.out.println(String.format("item: %s", item.getName()))); 63 | 64 | TenantContext.setCurrentTenant("1234567"); 65 | Stream.of(1, 2, 3).forEach(i -> { 66 | System.out.println(String.format("Adding user%d", i)); 67 | userRepo.save(new User(String.format("user%d", i), String.format("we have the best users, users%d", i), "1234567")); 68 | }); 69 | 70 | // This should be removed once we confirm that all instances of "user" have been removed 71 | userRepo.save(new User("user", "The auth user", "1234567")); 72 | 73 | userRepo.save(new User("janesmith", "The auth user", "1234567")); 74 | 75 | TenantContext.setCurrentTenant("12345678"); 76 | Stream.of(4, 5, 6).forEach(i -> { 77 | System.out.println(String.format("Adding item%d", i)); 78 | userRepo.save(new User(String.format("user%d", i), String.format("we have the best users, users%d", i), "12345678")); 79 | }); 80 | 81 | 82 | System.out.println(String.format("Users in DB %d", userRepo.count())); 83 | userRepo.findAll().forEach(item -> System.out.println(String.format("user: %s", item.getName()))); 84 | } 85 | 86 | }; 87 | } 88 | 89 | 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/hawk/Config.java: -------------------------------------------------------------------------------- 1 | package hawk; 2 | 3 | import hawk.service.SearchService; 4 | import hawk.service.UserSearchService; 5 | import hawk.service.UserService; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 9 | 10 | @Configuration 11 | public class Config implements WebMvcConfigurer { 12 | @Bean 13 | public SearchService searchService(){ 14 | return new SearchService(); 15 | } 16 | @Bean 17 | public UserSearchService userSearchService() { return new UserSearchService(); } 18 | @Bean 19 | public UserService userService() { return new UserService(); } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/hawk/MultiHttpSecurityConfig.java: -------------------------------------------------------------------------------- 1 | package hawk; 2 | 3 | import hawk.api.jwt.JwtConfigurer; 4 | import hawk.api.jwt.JwtTokenProvider; 5 | import hawk.api.token.TokenFilter; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | import org.springframework.core.annotation.Order; 11 | import org.springframework.security.authentication.AuthenticationManager; 12 | import org.springframework.security.authentication.BadCredentialsException; 13 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 14 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 15 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 16 | import org.springframework.security.config.http.SessionCreationPolicy; 17 | import org.springframework.security.core.Authentication; 18 | import org.springframework.security.core.AuthenticationException; 19 | import org.springframework.security.core.userdetails.User; 20 | import org.springframework.security.core.userdetails.UserDetails; 21 | import org.springframework.security.core.userdetails.UserDetailsService; 22 | import org.springframework.security.provisioning.InMemoryUserDetailsManager; 23 | import org.springframework.security.web.access.ExceptionTranslationFilter; 24 | import org.springframework.security.web.authentication.Http403ForbiddenEntryPoint; 25 | 26 | @EnableWebSecurity 27 | public class MultiHttpSecurityConfig { 28 | 29 | 30 | @Configuration 31 | @Order(1) 32 | public static class JwtWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter { 33 | private final JwtTokenProvider jwtTokenProvider; 34 | 35 | @Autowired 36 | public JwtWebSecurityConfigurationAdapter(JwtTokenProvider jwtTokenProvider) { 37 | this.jwtTokenProvider = jwtTokenProvider; 38 | } 39 | 40 | @Bean 41 | @Override 42 | public AuthenticationManager authenticationManagerBean() throws Exception { 43 | return super.authenticationManagerBean(); 44 | } 45 | 46 | protected void configure(HttpSecurity http) throws Exception { 47 | http 48 | .antMatcher("/api/jwt/**") 49 | .httpBasic().disable() 50 | .csrf().disable() 51 | .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) 52 | .and() 53 | .authorizeRequests() 54 | .antMatchers("/api/jwt/auth/signin").permitAll() 55 | .anyRequest().authenticated() 56 | .and() 57 | .apply(new JwtConfigurer(jwtTokenProvider)); 58 | } 59 | } 60 | 61 | @Configuration 62 | @Order(2) 63 | public static class TokenWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter { 64 | 65 | @Value("${token.http.auth.name:SH_AUTH_TOKEN}") 66 | private String authHeaderName; 67 | 68 | @Value("${token.http.auth.value:ITSASECRET}") 69 | private String authHeaderValue; 70 | 71 | protected void configure(HttpSecurity http) throws Exception { 72 | 73 | TokenFilter filter = new TokenFilter(authHeaderName); 74 | 75 | filter.setAuthenticationManager(new AuthenticationManager() { 76 | @Override 77 | public Authentication authenticate(Authentication authentication) throws AuthenticationException { 78 | String principal = (String) authentication.getPrincipal(); 79 | 80 | if (!authHeaderValue.equals(principal)) { 81 | throw new BadCredentialsException("The API key was not found or not the expected value."); 82 | } 83 | authentication.setAuthenticated(true); 84 | return authentication; 85 | } 86 | }); 87 | 88 | http 89 | .antMatcher("/api/token/**") 90 | .httpBasic().disable() 91 | .csrf().disable() 92 | .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) 93 | .and() 94 | .addFilter(filter) 95 | .addFilterBefore(new ExceptionTranslationFilter( 96 | new Http403ForbiddenEntryPoint()), 97 | filter.getClass() 98 | ) 99 | .authorizeRequests() 100 | .anyRequest() 101 | .authenticated(); 102 | } 103 | } 104 | 105 | @Configuration 106 | @Order(3) 107 | public static class BasicAuthWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter { 108 | @Override 109 | protected void configure(HttpSecurity http) throws Exception { 110 | http 111 | .antMatcher("/api/basic/**") 112 | .csrf().disable() 113 | .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) 114 | .and() 115 | .authorizeRequests().anyRequest().authenticated() 116 | .and() 117 | .httpBasic(); 118 | } 119 | } 120 | 121 | @Configuration 122 | @Order(5) 123 | public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter { 124 | @Override 125 | protected void configure(HttpSecurity http) throws Exception { 126 | http 127 | .authorizeRequests() 128 | .antMatchers( 129 | "/", 130 | "/jwt-auth", 131 | "/token-auth", 132 | "/basic-auth", 133 | "/openapi/**", 134 | "/openapi.yaml", 135 | "/swagger-ui/**", 136 | "/swagger-ui.html", 137 | "/log4j", 138 | "/hidden", 139 | "/hidden/*", 140 | "/login-code", 141 | "/login-form-multi" 142 | ).permitAll() 143 | .anyRequest().authenticated() 144 | .and() 145 | .formLogin() 146 | .loginPage("/login") 147 | .permitAll() 148 | .and() 149 | .logout() 150 | .logoutSuccessUrl("/") 151 | .permitAll(); 152 | } 153 | } 154 | 155 | @Bean 156 | public UserDetailsService userDetailsService() { 157 | UserDetails user = 158 | User.withDefaultPasswordEncoder() 159 | .username("user") 160 | .password("password") 161 | .roles("USER") 162 | .build(); 163 | 164 | UserDetails user2 = 165 | User.withDefaultPasswordEncoder() 166 | .username("janesmith") 167 | .password("password") 168 | .roles("USER") 169 | .build(); 170 | 171 | return new InMemoryUserDetailsManager(user, user2); 172 | } 173 | 174 | // "/api/okta/**" 175 | 176 | @Configuration 177 | @Order(4) 178 | public static class OktaWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter { 179 | @Override 180 | protected void configure(HttpSecurity http) throws Exception { 181 | http.antMatcher("/api/okta/**") 182 | .httpBasic().disable() 183 | .csrf().disable() 184 | .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) 185 | .and() 186 | .authorizeRequests() 187 | .antMatchers("/api/okta/**") 188 | .permitAll(); 189 | ; 190 | } 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/main/java/hawk/api/AuthenticationRequest.java: -------------------------------------------------------------------------------- 1 | package hawk.api; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | import java.io.Serializable; 9 | 10 | @Data 11 | @Builder 12 | @NoArgsConstructor 13 | @AllArgsConstructor 14 | public class AuthenticationRequest implements Serializable { 15 | private static final long serialVersionUID = -6986746375915710855L; 16 | private String username; 17 | private String password; 18 | private String tenant; 19 | } -------------------------------------------------------------------------------- /src/main/java/hawk/api/ExtraAuthenticationRequest.java: -------------------------------------------------------------------------------- 1 | package hawk.api; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | import java.io.Serializable; 9 | 10 | @Data 11 | @Builder 12 | @NoArgsConstructor 13 | @AllArgsConstructor 14 | public class ExtraAuthenticationRequest implements Serializable { 15 | private static final long serialVersionUID = -6986746375915710855L; 16 | private String username; 17 | private String password; 18 | private String remember; 19 | private String loginCode; 20 | } -------------------------------------------------------------------------------- /src/main/java/hawk/api/SearchResult.java: -------------------------------------------------------------------------------- 1 | package hawk.api; 2 | 3 | import hawk.entity.Item; 4 | 5 | import java.util.List; 6 | 7 | public class SearchResult { 8 | 9 | private final String searchText; 10 | private final List items; 11 | 12 | public SearchResult(String searchText, List items) { 13 | this.searchText = searchText; 14 | this.items = items; 15 | } 16 | 17 | public String getSearchText() { 18 | return searchText; 19 | } 20 | 21 | public List getItems() { 22 | return items; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/hawk/api/basic/BasicAuthItemController.java: -------------------------------------------------------------------------------- 1 | package hawk.api.basic; 2 | 3 | import hawk.form.Search; 4 | import hawk.service.SearchService; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.http.ResponseEntity; 7 | import org.springframework.web.bind.annotation.GetMapping; 8 | import org.springframework.web.bind.annotation.PathVariable; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | import org.springframework.web.bind.annotation.RestController; 11 | 12 | @RestController 13 | @RequestMapping("/api/basic/items") 14 | public class BasicAuthItemController { 15 | 16 | private final SearchService searchService; 17 | 18 | @Autowired 19 | public BasicAuthItemController(SearchService searchService) { 20 | this.searchService = searchService; 21 | } 22 | 23 | @GetMapping("/search/") 24 | public ResponseEntity search() { 25 | Search search = new Search(""); 26 | return ResponseEntity.ok(searchService.search(search)); 27 | } 28 | 29 | @GetMapping("/search/{text}") 30 | public ResponseEntity search(@PathVariable("text") String text) { 31 | Search search = new Search(text); 32 | return ResponseEntity.ok(searchService.search(search)); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/hawk/api/jwt/JwtAuthController.java: -------------------------------------------------------------------------------- 1 | package hawk.api.jwt; 2 | 3 | import hawk.api.AuthenticationRequest; 4 | import hawk.repos.UserRepo; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.http.ResponseEntity; 7 | import org.springframework.security.authentication.AuthenticationManager; 8 | import org.springframework.security.authentication.BadCredentialsException; 9 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 10 | import org.springframework.security.core.AuthenticationException; 11 | import org.springframework.security.core.GrantedAuthority; 12 | import org.springframework.security.core.userdetails.UserDetails; 13 | import org.springframework.security.core.userdetails.UserDetailsService; 14 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 15 | import org.springframework.web.bind.annotation.PostMapping; 16 | import org.springframework.web.bind.annotation.RequestBody; 17 | import org.springframework.web.bind.annotation.RequestMapping; 18 | import org.springframework.web.bind.annotation.RestController; 19 | 20 | import java.util.HashMap; 21 | import java.util.Map; 22 | import java.util.stream.Collectors; 23 | 24 | @RestController 25 | @RequestMapping("/api/jwt/auth") 26 | public class JwtAuthController { 27 | 28 | private final AuthenticationManager authenticationManager; 29 | private final JwtTokenProvider jwtTokenProvider; 30 | private final UserDetailsService userDetailsService; 31 | private final UserRepo userRepo; 32 | 33 | @Autowired 34 | public JwtAuthController(AuthenticationManager authenticationManager, JwtTokenProvider jwtTokenProvider, UserDetailsService userDetailsService, 35 | UserRepo userRepo) { 36 | this.authenticationManager = authenticationManager; 37 | this.jwtTokenProvider = jwtTokenProvider; 38 | this.userDetailsService = userDetailsService; 39 | this.userRepo = userRepo; 40 | } 41 | 42 | @PostMapping("/signin") 43 | public ResponseEntity signin(@RequestBody AuthenticationRequest data) { 44 | try { 45 | String username = data.getUsername(); 46 | authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, data.getPassword())); 47 | UserDetails userDetails = this.userDetailsService.loadUserByUsername(username); 48 | if (null == userDetails) { 49 | throw new UsernameNotFoundException("username"); 50 | } 51 | 52 | String tenantId = this.userRepo.findByName(username).getTenantId(); 53 | String token = jwtTokenProvider.createToken(username, 54 | userDetails.getAuthorities().stream().map(auth -> ((GrantedAuthority) auth).toString()).collect(Collectors.toList()), 55 | tenantId); 56 | Map model = new HashMap<>(); 57 | model.put("username", username); 58 | model.put("token", token); 59 | return ResponseEntity.ok(model); 60 | } catch (AuthenticationException e) { 61 | throw new BadCredentialsException("Invalid username/password supplied"); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/hawk/api/jwt/JwtConfigurer.java: -------------------------------------------------------------------------------- 1 | package hawk.api.jwt; 2 | 3 | import org.springframework.security.config.annotation.SecurityConfigurerAdapter; 4 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 5 | import org.springframework.security.web.DefaultSecurityFilterChain; 6 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 7 | 8 | public class JwtConfigurer extends SecurityConfigurerAdapter { 9 | 10 | private JwtTokenProvider jwtTokenProvider; 11 | 12 | public JwtConfigurer(JwtTokenProvider jwtTokenProvider) { 13 | this.jwtTokenProvider = jwtTokenProvider; 14 | } 15 | 16 | @Override 17 | public void configure(HttpSecurity http) throws Exception { 18 | JwtFilter customFilter = new JwtFilter(jwtTokenProvider); 19 | http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/hawk/api/jwt/JwtFilter.java: -------------------------------------------------------------------------------- 1 | package hawk.api.jwt; 2 | 3 | import hawk.api.jwt.JwtTokenProvider; 4 | import hawk.context.TenantContext; 5 | import org.springframework.security.core.Authentication; 6 | import org.springframework.security.core.context.SecurityContextHolder; 7 | import org.springframework.web.filter.GenericFilterBean; 8 | 9 | import javax.servlet.FilterChain; 10 | import javax.servlet.ServletException; 11 | import javax.servlet.ServletRequest; 12 | import javax.servlet.ServletResponse; 13 | import javax.servlet.http.HttpServletRequest; 14 | import java.io.IOException; 15 | 16 | public class JwtFilter extends GenericFilterBean { 17 | 18 | private JwtTokenProvider jwtTokenProvider; 19 | 20 | public JwtFilter(JwtTokenProvider jwtTokenProvider) { 21 | this.jwtTokenProvider = jwtTokenProvider; 22 | } 23 | 24 | @Override 25 | public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain) 26 | throws IOException, ServletException { 27 | String token = jwtTokenProvider.resolveToken((HttpServletRequest) req); 28 | 29 | if (token != null && jwtTokenProvider.validateToken(token)) { 30 | Authentication auth = token != null ? jwtTokenProvider.getAuthentication(token) : null; 31 | SecurityContextHolder.getContext().setAuthentication(auth); 32 | TenantContext.setCurrentTenant(jwtTokenProvider.getTenantId(token)); 33 | } 34 | 35 | filterChain.doFilter(req, res); 36 | } 37 | } -------------------------------------------------------------------------------- /src/main/java/hawk/api/jwt/JwtItemController.java: -------------------------------------------------------------------------------- 1 | package hawk.api.jwt; 2 | 3 | import hawk.api.SearchResult; 4 | import hawk.form.Search; 5 | import hawk.repos.ItemsRepo; 6 | import hawk.service.SearchService; 7 | import lombok.val; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.http.ResponseEntity; 10 | import org.springframework.web.bind.annotation.GetMapping; 11 | import org.springframework.web.bind.annotation.Mapping; 12 | import org.springframework.web.bind.annotation.PathVariable; 13 | import org.springframework.web.bind.annotation.PostMapping; 14 | import org.springframework.web.bind.annotation.RequestBody; 15 | import org.springframework.web.bind.annotation.RequestMapping; 16 | import org.springframework.web.bind.annotation.RestController; 17 | 18 | @RestController 19 | @RequestMapping("/api/jwt/items") 20 | public class JwtItemController { 21 | 22 | @Autowired 23 | ItemsRepo repo; 24 | 25 | private final SearchService searchService; 26 | 27 | @Autowired 28 | public JwtItemController(SearchService searchService) { 29 | this.searchService = searchService; 30 | } 31 | 32 | @GetMapping("/search/") 33 | public ResponseEntity searchAll() { 34 | Search search = new Search(""); 35 | return ResponseEntity.ok(searchService.search(search)); 36 | } 37 | 38 | @GetMapping("/search/{text}") 39 | public ResponseEntity search(@PathVariable("text") String text) { 40 | Search search = new Search(text); 41 | return ResponseEntity.ok(searchService.search(search)); 42 | } 43 | 44 | @PostMapping("/search") 45 | public ResponseEntity search(@RequestBody Search search) { 46 | SearchResult result = new SearchResult(search.getSearchText(), searchService.search(search)); 47 | return ResponseEntity.ok(result); 48 | } 49 | 50 | // @PathVariable("id") String id should be types correctly as a Long. eg: @PathVariable("id") Long id 51 | @GetMapping("/{id}") 52 | public ResponseEntity getById(@PathVariable("id") String id) { 53 | val item = repo.findById(Long.getLong(id)); 54 | return ResponseEntity.ok(item); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/hawk/api/jwt/JwtLog4jController.java: -------------------------------------------------------------------------------- 1 | package hawk.api.jwt; 2 | 3 | import org.apache.logging.log4j.LogManager; 4 | import org.apache.logging.log4j.Logger; 5 | import org.springframework.http.ResponseEntity; 6 | import org.springframework.stereotype.Controller; 7 | import org.springframework.web.bind.annotation.GetMapping; 8 | import org.springframework.web.bind.annotation.PathVariable; 9 | import org.springframework.web.bind.annotation.RequestParam; 10 | 11 | import javax.servlet.http.HttpServletRequest; 12 | import java.util.Enumeration; 13 | 14 | @Controller 15 | public class JwtLog4jController { 16 | 17 | private static final Logger logger = LogManager.getLogger(JwtLog4jController.class); 18 | 19 | @GetMapping("/log4j") 20 | public ResponseEntity logRequest(@RequestParam String text, HttpServletRequest request) { 21 | Enumeration headers = request.getHeaderNames(); 22 | while (headers.hasMoreElements()) { 23 | String it = headers.nextElement(); 24 | logger.info("{} = {}", it, request.getHeader(it)); 25 | } 26 | logger.info("Hitting Log4J route"); 27 | logger.info(text); 28 | return ResponseEntity.ok().build(); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/hawk/api/jwt/JwtTokenProvider.java: -------------------------------------------------------------------------------- 1 | package hawk.api.jwt; 2 | 3 | import io.jsonwebtoken.*; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.beans.factory.annotation.Value; 6 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 7 | import org.springframework.security.core.Authentication; 8 | import org.springframework.security.core.userdetails.UserDetails; 9 | import org.springframework.security.core.userdetails.UserDetailsService; 10 | import org.springframework.stereotype.Component; 11 | 12 | import javax.annotation.PostConstruct; 13 | import javax.servlet.http.HttpServletRequest; 14 | import java.util.Base64; 15 | import java.util.Date; 16 | import java.util.List; 17 | 18 | @Component 19 | public class JwtTokenProvider { 20 | 21 | @Value("${security.jwt.token.secret-key:supersecretkeydonttellimseriousitsabigthing}") 22 | private String secretKey = "secret"; 23 | 24 | @Value("${security.jwt.token.expire-length:3600000}") 25 | private long validityInMilliseconds = 3600000; // 1h 26 | 27 | private final UserDetailsService userDetailsService; 28 | 29 | @Autowired 30 | public JwtTokenProvider(UserDetailsService userDetailsService) { 31 | this.userDetailsService = userDetailsService; 32 | } 33 | 34 | @PostConstruct 35 | protected void init() { 36 | secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes()); 37 | } 38 | 39 | public String createToken(String username, List roles, String companyId) { 40 | Claims claims = Jwts.claims().setSubject(username); 41 | claims.put("roles", roles); 42 | claims.put("tenantId", companyId); 43 | Date now = new Date(); 44 | Date validity = new Date(now.getTime() + validityInMilliseconds); 45 | return Jwts.builder()// 46 | .setClaims(claims)// 47 | .setIssuedAt(now)// 48 | .setExpiration(validity)// 49 | .signWith(SignatureAlgorithm.HS256, secretKey)// 50 | .compact(); 51 | } 52 | 53 | public Authentication getAuthentication(String token) { 54 | UserDetails userDetails = this.userDetailsService.loadUserByUsername(getUsername(token)); 55 | return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities()); 56 | } 57 | 58 | private String getUsername(String token) { 59 | return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject(); 60 | } 61 | 62 | public String getTenantId(String token) { 63 | return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().get("tenantId", String.class); 64 | } 65 | 66 | public String resolveToken(HttpServletRequest req) { 67 | String bearerToken = req.getHeader("Authorization"); 68 | if (bearerToken != null && bearerToken.startsWith("Bearer ")) { 69 | return bearerToken.substring(7, bearerToken.length()); 70 | } 71 | return null; 72 | } 73 | 74 | public boolean validateToken(String token) { 75 | try { 76 | Jws claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token); 77 | return !claims.getBody().getExpiration().before(new Date()); 78 | } catch (JwtException | IllegalArgumentException e) { 79 | throw new RuntimeException("Expired or invalid JWT token"); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/hawk/api/jwt/JwtUserController.java: -------------------------------------------------------------------------------- 1 | package hawk.api.jwt; 2 | 3 | import hawk.entity.User; 4 | import hawk.form.Search; 5 | import hawk.service.UserSearchService; 6 | import hawk.service.UserService; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.http.ResponseEntity; 9 | import org.springframework.web.bind.annotation.GetMapping; 10 | import org.springframework.web.bind.annotation.PathVariable; 11 | import org.springframework.web.bind.annotation.RequestMapping; 12 | import org.springframework.web.bind.annotation.RestController; 13 | 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | 17 | @RestController 18 | @RequestMapping("/api/jwt/users") 19 | public class JwtUserController { 20 | 21 | private final UserService userService; 22 | private final UserSearchService userSearchService; 23 | 24 | @Autowired 25 | public JwtUserController(UserService userService, UserSearchService userSearchService) { 26 | this.userService = userService; 27 | this.userSearchService = userSearchService; 28 | } 29 | 30 | @GetMapping("/search/") 31 | public ResponseEntity> searchAll() { 32 | Search search = new Search(""); 33 | return ResponseEntity.ok(this.userService.findUsersByName("")); 34 | } 35 | 36 | @GetMapping("/search/{text}") 37 | public ResponseEntity search(@PathVariable("text") String text) { 38 | Search search = new Search(text); 39 | return ResponseEntity.ok(this.userService.findUsersByName(search.getSearchText())); 40 | } 41 | 42 | @GetMapping("/search/bad/{text}") 43 | public ResponseEntity searchCrappy(@PathVariable("text") String text) { 44 | Search search = new Search(text); 45 | return ResponseEntity.ok(this.userSearchService.search(search)); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/hawk/api/okta/OktaController.java: -------------------------------------------------------------------------------- 1 | package hawk.api.okta; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.http.ResponseEntity; 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | import org.springframework.web.bind.annotation.RequestMapping; 7 | import org.springframework.web.bind.annotation.RestController; 8 | 9 | import javax.servlet.http.HttpServletRequest; 10 | import java.util.Enumeration; 11 | 12 | @RestController 13 | @RequestMapping("/api/okta") 14 | public class OktaController { 15 | 16 | @GetMapping("/me/token") 17 | public ResponseEntity me(HttpServletRequest request) { 18 | Enumeration headerNames = request.getHeaderNames(); 19 | while (headerNames.hasMoreElements()) { 20 | String name = headerNames.nextElement(); 21 | String value = request.getHeader(name); 22 | System.out.println(name + ": " + value); 23 | } 24 | String authToken = request.getHeader("Authorization"); 25 | if (authToken == null || !authToken.startsWith("Bearer ")) { 26 | return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); 27 | } 28 | OktaIdInfo oktaInfo = new OktaIdInfo(authToken); 29 | return ResponseEntity.ok(oktaInfo); 30 | } 31 | 32 | } 33 | 34 | -------------------------------------------------------------------------------- /src/main/java/hawk/api/okta/OktaIdInfo.java: -------------------------------------------------------------------------------- 1 | package hawk.api.okta; 2 | 3 | public class OktaIdInfo { 4 | private final String token; 5 | 6 | public OktaIdInfo(String token) { 7 | this.token = token; 8 | } 9 | 10 | public String getToken() { 11 | return token; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/hawk/api/token/TokenFilter.java: -------------------------------------------------------------------------------- 1 | package hawk.api.token; 2 | 3 | import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter; 4 | 5 | import javax.servlet.http.HttpServletRequest; 6 | 7 | public class TokenFilter extends AbstractPreAuthenticatedProcessingFilter { 8 | 9 | private String authHeaderName; 10 | 11 | public TokenFilter(String authHeaderName) { 12 | this.authHeaderName = authHeaderName; 13 | } 14 | 15 | @Override 16 | protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) { 17 | return request.getHeader(authHeaderName); 18 | } 19 | 20 | @Override 21 | protected Object getPreAuthenticatedCredentials(HttpServletRequest request) { 22 | return "N/A"; 23 | } 24 | } -------------------------------------------------------------------------------- /src/main/java/hawk/api/token/TokenItemController.java: -------------------------------------------------------------------------------- 1 | package hawk.api.token; 2 | 3 | import hawk.form.Search; 4 | import hawk.service.SearchService; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.http.ResponseEntity; 7 | import org.springframework.web.bind.annotation.GetMapping; 8 | import org.springframework.web.bind.annotation.PathVariable; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | import org.springframework.web.bind.annotation.RestController; 11 | 12 | @RestController 13 | @RequestMapping("/api/token/items") 14 | public class TokenItemController { 15 | 16 | private final SearchService searchService; 17 | 18 | @Autowired 19 | public TokenItemController(SearchService searchService) { 20 | this.searchService = searchService; 21 | } 22 | 23 | @GetMapping("/search/") 24 | public ResponseEntity search() { 25 | Search search = new Search(""); 26 | return ResponseEntity.ok(searchService.search(search)); 27 | } 28 | 29 | @GetMapping("/search/{text}") 30 | public ResponseEntity search(@PathVariable("text") String text) { 31 | Search search = new Search(text); 32 | return ResponseEntity.ok(searchService.search(search)); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/hawk/aspect/UserServiceAspect.java: -------------------------------------------------------------------------------- 1 | package hawk.aspect; 2 | 3 | import hawk.context.TenantContext; 4 | import org.aspectj.lang.JoinPoint; 5 | import org.aspectj.lang.annotation.Aspect; 6 | import org.aspectj.lang.annotation.Before; 7 | import org.hibernate.Session; 8 | import org.springframework.stereotype.Component; 9 | import hawk.service.UserService; 10 | 11 | @Aspect 12 | @Component 13 | public class UserServiceAspect { 14 | @Before("execution(* hawk.service.UserService.*(..))&& target(userService) ") 15 | public void aroundExecution(JoinPoint pjp, UserService userService) throws Throwable { 16 | org.hibernate.Filter filter = userService.entityManager.unwrap(Session.class).enableFilter("tenantFilter"); 17 | filter.setParameter("tenantId", TenantContext.getCurrentTenant()); 18 | filter.validate(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/hawk/context/TenantContext.java: -------------------------------------------------------------------------------- 1 | package hawk.context; 2 | 3 | public class TenantContext { 4 | private static ThreadLocal currentTenant = new InheritableThreadLocal<>(); 5 | 6 | public static String getCurrentTenant() { 7 | return currentTenant.get(); 8 | } 9 | 10 | public static void setCurrentTenant(String tenant) { 11 | currentTenant.set(tenant); 12 | } 13 | 14 | public static void clear() { 15 | currentTenant.set(null); 16 | } 17 | } -------------------------------------------------------------------------------- /src/main/java/hawk/controller/AdminController.java: -------------------------------------------------------------------------------- 1 | package hawk.controller; 2 | 3 | import hawk.entity.User; 4 | import hawk.form.Search; 5 | import hawk.service.UserSearchService; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.stereotype.Controller; 8 | import org.springframework.ui.Model; 9 | import org.springframework.web.bind.annotation.GetMapping; 10 | import org.springframework.web.bind.annotation.ModelAttribute; 11 | import org.springframework.web.bind.annotation.PostMapping; 12 | 13 | import java.util.List; 14 | 15 | @Controller 16 | public class AdminController { 17 | @Autowired 18 | UserSearchService userSearchService; 19 | 20 | @GetMapping("/admin") 21 | public String index(Model model) { 22 | model.addAttribute("title", "Admin"); 23 | return "admin"; 24 | } 25 | 26 | @GetMapping("/admin/users") 27 | public String users(Model model) { 28 | model.addAttribute("title", "Users"); 29 | return "users"; 30 | } 31 | 32 | @GetMapping("/admin/companies") 33 | public String companies(Model model) { 34 | model.addAttribute("title", "Companies"); 35 | return "companies"; 36 | } 37 | 38 | @GetMapping( "/admin/search") 39 | public String searchForm(Model model) { 40 | model.addAttribute("search", new Search()); 41 | model.addAttribute("title", "User Search"); 42 | return "user-search"; 43 | } 44 | 45 | @PostMapping( "/admin/search") 46 | public String searchSubmit(@ModelAttribute Search search, Model model) { 47 | List users = userSearchService.search(search); 48 | model.addAttribute("users", users); 49 | model.addAttribute("search", search); 50 | model.addAttribute("title", "User Search"); 51 | return "user-search"; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/hawk/controller/HiddenController.java: -------------------------------------------------------------------------------- 1 | package hawk.controller; 2 | 3 | import org.springframework.stereotype.Controller; 4 | import org.springframework.ui.Model; 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | import org.springframework.web.bind.annotation.PathVariable; 7 | 8 | @Controller 9 | public class HiddenController { 10 | 11 | @GetMapping("/hidden") 12 | public String index(Model model) { 13 | model.addAttribute("title", "Hidden Page"); 14 | return "hidden"; 15 | } 16 | 17 | @GetMapping("/hidden/hidden2") 18 | public String hidden(Model model) { 19 | model.addAttribute("Rando hidden page"); 20 | return "hidden2"; 21 | } 22 | 23 | @GetMapping("/hidden/cypress") 24 | public String cypress(Model model) { 25 | model.addAttribute("title", "Hidden Page, found and tested with cypress tests"); 26 | return "hidden"; 27 | } 28 | 29 | @GetMapping("/hidden/selenium") 30 | public String selenium(Model model) { 31 | model.addAttribute("title", "Hidden Page, found and tested with selenium tests"); 32 | return "hidden"; 33 | } 34 | 35 | @GetMapping("/hidden/playwright") 36 | public String playwright(Model model) { 37 | model.addAttribute("title", "Hidden Page, found and tested with playwright tests"); 38 | return "hidden"; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/hawk/controller/IndexController.java: -------------------------------------------------------------------------------- 1 | package hawk.controller; 2 | 3 | import org.springframework.stereotype.Controller; 4 | import org.springframework.ui.Model; 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | 7 | @Controller 8 | public class IndexController { 9 | 10 | @GetMapping("/") 11 | public String index(Model model) { 12 | model.addAttribute("title", "StackHawk Java Vulny Application"); 13 | return "index"; 14 | } 15 | 16 | @GetMapping("/jwt-auth") 17 | public String jwtAuth(Model model) { 18 | model.addAttribute("title", "JWT Auth"); 19 | return "jwt-auth"; 20 | } 21 | 22 | @GetMapping("/token-auth") 23 | public String tokenAuth(Model model) { 24 | model.addAttribute("title", "Token Auth"); 25 | return "token-auth"; 26 | } 27 | 28 | @GetMapping("/basic-auth") 29 | public String basicAuth(Model model) { 30 | model.addAttribute("title", "Basic Auth"); 31 | return "basic-auth"; 32 | } 33 | } -------------------------------------------------------------------------------- /src/main/java/hawk/controller/LinksController.java: -------------------------------------------------------------------------------- 1 | package hawk.controller; 2 | 3 | import org.apache.commons.lang3.StringUtils; 4 | import org.springframework.stereotype.Controller; 5 | import org.springframework.ui.Model; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | import org.springframework.web.servlet.HandlerMapping; 8 | 9 | import javax.servlet.http.HttpServletRequest; 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | @Controller 14 | public class LinksController { 15 | 16 | @GetMapping(value = {"/links/**"}) 17 | public String getPayload(Model model, HttpServletRequest request) { 18 | 19 | String fullPath = (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE); 20 | String subPath = StringUtils.removeStart(fullPath, "/links"); 21 | 22 | List links = new ArrayList<>(); 23 | links.add("blah"); 24 | model.addAttribute("links", links); 25 | 26 | return "links"; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/hawk/controller/LoginController.java: -------------------------------------------------------------------------------- 1 | package hawk.controller; 2 | 3 | import hawk.api.ExtraAuthenticationRequest; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.security.authentication.AuthenticationManager; 6 | import org.springframework.security.authentication.BadCredentialsException; 7 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 8 | import org.springframework.security.core.Authentication; 9 | import org.springframework.security.core.AuthenticationException; 10 | import org.springframework.security.core.context.SecurityContext; 11 | import org.springframework.security.core.context.SecurityContextHolder; 12 | import org.springframework.security.core.userdetails.UserDetails; 13 | import org.springframework.security.core.userdetails.UserDetailsService; 14 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 15 | import org.springframework.stereotype.Controller; 16 | import org.springframework.ui.Model; 17 | import org.springframework.web.bind.annotation.CookieValue; 18 | import org.springframework.web.bind.annotation.GetMapping; 19 | import org.springframework.web.bind.annotation.ModelAttribute; 20 | import org.springframework.web.bind.annotation.PostMapping; 21 | 22 | import javax.servlet.http.Cookie; 23 | import javax.servlet.http.HttpServletRequest; 24 | import javax.servlet.http.HttpServletResponse; 25 | import javax.servlet.http.HttpSession; 26 | import java.util.Map; 27 | import java.util.UUID; 28 | import java.util.concurrent.ConcurrentHashMap; 29 | 30 | import static org.springframework.security.web.context.HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY; 31 | 32 | @Controller 33 | public class LoginController { 34 | 35 | private final AuthenticationManager authenticationManager; 36 | private final UserDetailsService userDetailsService; 37 | 38 | @Autowired 39 | public LoginController(AuthenticationManager authenticationManager, UserDetailsService userDetailsService) { 40 | this.authenticationManager = authenticationManager; 41 | this.userDetailsService = userDetailsService; 42 | } 43 | 44 | @GetMapping("/login") 45 | public String login(Model model) { 46 | model.addAttribute("title", "Login title"); 47 | return "login"; 48 | } 49 | 50 | Map loginCodes = new ConcurrentHashMap<>(); 51 | 52 | @GetMapping("/login-code") 53 | public String loginCode(HttpServletRequest req, HttpServletResponse resp, Model model) { 54 | String sessId = req.getSession().getId(); 55 | String cookieCode = UUID.randomUUID().toString(); 56 | loginCodes.put("cookie-" + sessId, cookieCode); 57 | resp.addCookie(new Cookie("XLOGINID", cookieCode)); 58 | return "redirect:/login-form-multi"; 59 | } 60 | 61 | @GetMapping("/login-form-multi") 62 | public String loginFormMulti(HttpServletRequest req, Model model) { 63 | String sessId = req.getSession().getId(); 64 | String loginCode = loginCodes.get("cookie-" + sessId); 65 | if (loginCode == null) { 66 | return "redirect:/login-code"; 67 | } 68 | model.addAttribute("title", "Login multi title"); 69 | model.addAttribute("loginCode", loginCode); 70 | return "login-form-multi"; 71 | } 72 | 73 | @PostMapping("/login-form-multi") 74 | public String loginFormMulti(HttpServletRequest req, 75 | HttpServletResponse resp, 76 | @CookieValue("XLOGINID") String xLoginId, 77 | @ModelAttribute ExtraAuthenticationRequest data) { 78 | try { 79 | String sessId = req.getSession().getId(); 80 | String loginCode = loginCodes.get("cookie-" + sessId); 81 | 82 | if (data.getLoginCode() == null 83 | || data.getRemember() == null 84 | || data.getLoginCode().isEmpty() 85 | || data.getRemember().isEmpty() 86 | || !data.getLoginCode().equals(loginCode) 87 | || !xLoginId.equals(loginCode)) { 88 | throw new BadCredentialsException("missing required fields"); 89 | } 90 | String username = data.getUsername(); 91 | Authentication auth = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, data.getPassword())); 92 | UserDetails userDetails = this.userDetailsService.loadUserByUsername(username); 93 | if (null == userDetails) { 94 | throw new UsernameNotFoundException("username"); 95 | } 96 | 97 | SecurityContext sc = SecurityContextHolder.getContext(); 98 | sc.setAuthentication(auth); 99 | HttpSession session = req.getSession(true); 100 | session.setAttribute(SPRING_SECURITY_CONTEXT_KEY, sc); 101 | 102 | return "redirect:/"; 103 | } catch (AuthenticationException e) { 104 | throw new BadCredentialsException("Invalid username/password supplied"); 105 | } 106 | } 107 | 108 | @GetMapping("/login-multi-check") 109 | public String loginCheck(HttpServletRequest req, HttpServletResponse resp, Model model, @CookieValue("XLOGINID") String xLoginId) { 110 | String sessId = req.getSession().getId(); 111 | String loginCode = loginCodes.get("cookie-" + sessId); 112 | if (loginCode == null || !loginCode.equals(xLoginId)) 113 | return "redirect:/login-form-multi"; 114 | model.addAttribute("title", "StackHawk Java Vulny Application"); 115 | return "index"; 116 | } 117 | 118 | } -------------------------------------------------------------------------------- /src/main/java/hawk/controller/PayloadController.java: -------------------------------------------------------------------------------- 1 | package hawk.controller; 2 | 3 | import java.io.ByteArrayInputStream; 4 | import java.io.ByteArrayOutputStream; 5 | import java.io.IOException; 6 | import java.io.OutputStream; 7 | import java.util.Map; 8 | import java.util.Random; 9 | import java.util.concurrent.ConcurrentHashMap; 10 | import org.apache.commons.compress.utils.IOUtils; 11 | import org.apache.commons.lang3.RandomStringUtils; 12 | import org.springframework.beans.factory.annotation.Value; 13 | import org.springframework.stereotype.Controller; 14 | import org.springframework.ui.Model; 15 | import org.springframework.web.bind.annotation.GetMapping; 16 | import org.springframework.web.bind.annotation.PathVariable; 17 | import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; 18 | 19 | @Controller 20 | public class PayloadController { 21 | 22 | private static final char[] chars = new char[]{'a','b','c',' ','\n'}; 23 | private static Map payloadCache = new ConcurrentHashMap(); 24 | 25 | @Value("${payload.start-size:2048}") 26 | private int startPayloadSize = 2048; 27 | 28 | @Value("${payload.count:10}") 29 | private int payloadCount = 20; 30 | 31 | @Value("${payload.delayStart:0}") 32 | private int payloadDelayStart = 0; 33 | 34 | @Value("${payload.delayEnd:0}") 35 | private int payloadDelayEnd = 0; 36 | 37 | public void sleepy() { 38 | if ((payloadDelayStart > 0 || payloadDelayEnd > 0) && payloadDelayEnd > payloadDelayStart) { 39 | Random random = new Random(); 40 | Long sleepFor = random.nextLong(payloadDelayStart, payloadDelayEnd); 41 | try { 42 | Thread.sleep(sleepFor); 43 | } catch (InterruptedException e) { 44 | e.printStackTrace(); 45 | } 46 | } 47 | } 48 | 49 | @GetMapping(value={"/payload/{size}","/admin/payload/{size}"}) 50 | public String getPayload(Model model, 51 | @PathVariable("size") Integer size) { 52 | 53 | sleepy(); 54 | if(payloadCache.containsKey(size)){ 55 | model.addAttribute("payload", new String( payloadCache.get(size))); 56 | }else { 57 | String tmpData = "mobile: 555-678-5343 "; 58 | String payload = tmpData + RandomStringUtils.random(size - tmpData.length(), 0, 5, false, false, chars); 59 | payloadCache.put(size, payload.getBytes()); 60 | model.addAttribute("payload", payload); 61 | } 62 | return "payload-view"; 63 | } 64 | 65 | @GetMapping(value={"/payloads", "/admin/payloads"}) 66 | public String getPayloadsList(Model model){ 67 | Integer[] payloadSizes = new Integer[payloadCount]; 68 | 69 | for(int i = 0; i < payloadCount; i++){ 70 | payloadSizes[i] = startPayloadSize + i; 71 | } 72 | 73 | model.addAttribute("payloads", payloadSizes); 74 | return "payloads"; 75 | } 76 | 77 | @GetMapping(value={"/payload/stream/{size}", "/admin/payload/stream/{size}"}) 78 | public StreamingResponseBody getPayloadStream(@PathVariable("size") Integer size) { 79 | String tmpData = "mobile: 555-678-5343 "; 80 | sleepy(); 81 | return new StreamingResponseBody() { 82 | Integer cnt = 0; 83 | @Override 84 | public void writeTo(OutputStream outputStream) throws IOException { 85 | 86 | if(payloadCache.containsKey(size)){ 87 | IOUtils.copy(new ByteArrayInputStream(payloadCache.get(size)), outputStream); 88 | }else { 89 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 90 | cnt += tmpData.length(); 91 | outputStream.write(tmpData.getBytes()); 92 | outputStream.flush(); 93 | while (cnt < size) { 94 | int rnSize = 1024; 95 | if ((cnt + rnSize) > size) { 96 | rnSize = size - cnt; 97 | } 98 | String nl = System.lineSeparator(); 99 | String tp = RandomStringUtils.random(rnSize, 0, 5, false, false, chars); 100 | byte[] data = (tp + nl).getBytes(); 101 | outputStream.write(data); 102 | outputStream.flush(); 103 | baos.write(data); 104 | cnt += data.length; 105 | } 106 | payloadCache.put(size, baos.toByteArray()); 107 | } 108 | } 109 | }; 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /src/main/java/hawk/controller/SearchController.java: -------------------------------------------------------------------------------- 1 | package hawk.controller; 2 | 3 | import hawk.entity.Item; 4 | import hawk.form.Search; 5 | import hawk.repos.ItemsRepo; 6 | import hawk.service.SearchService; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.stereotype.Controller; 9 | import org.springframework.ui.Model; 10 | import org.springframework.web.bind.annotation.GetMapping; 11 | import org.springframework.web.bind.annotation.ModelAttribute; 12 | import org.springframework.web.bind.annotation.PostMapping; 13 | 14 | import javax.persistence.EntityManager; 15 | import java.util.List; 16 | 17 | @Controller 18 | public class SearchController { 19 | 20 | @Autowired 21 | ItemsRepo repo; 22 | 23 | @Autowired 24 | EntityManager entityManager; 25 | 26 | @Autowired 27 | SearchService searchService; 28 | 29 | @GetMapping("/search") 30 | public String searchForm(Model model) { 31 | model.addAttribute("search", new Search()); 32 | model.addAttribute("title", "Search"); 33 | return "search"; 34 | } 35 | 36 | @PostMapping("/search") 37 | public String searchSubmit(@ModelAttribute Search search, Model model) { 38 | List items = searchService.search(search); 39 | model.addAttribute("items", items); 40 | model.addAttribute("search", search); 41 | model.addAttribute("title", "Search"); 42 | return "search"; 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/hawk/domain/Hotel.java: -------------------------------------------------------------------------------- 1 | package hawk.domain; 2 | 3 | import hawk.hotel.domain.Building; 4 | import hawk.hotel.domain.Continent; 5 | 6 | import javax.persistence.*; 7 | import javax.xml.bind.annotation.*; 8 | import java.util.Comparator; 9 | 10 | /* 11 | * a simple domain entity doubling as a DTO 12 | */ 13 | @Entity 14 | @Table(name = "hotel") 15 | @XmlRootElement 16 | @XmlAccessorType(XmlAccessType.FIELD) 17 | public class Hotel implements Comparable{ 18 | 19 | @Id 20 | @GeneratedValue() 21 | private long id; 22 | 23 | @Column(nullable = false) 24 | private String name; 25 | 26 | @Column() 27 | private String description; 28 | 29 | @Column() 30 | String city; 31 | 32 | @Column() 33 | private int rating; 34 | 35 | @Column() 36 | @Enumerated(EnumType.STRING) 37 | private Continent continent; 38 | 39 | public Hotel() { 40 | } 41 | 42 | public Hotel(String name, String description, int rating) { 43 | this.name = name; 44 | this.description = description; 45 | this.rating = rating; 46 | } 47 | 48 | public long getId() { 49 | return this.id; 50 | } 51 | 52 | // for tests ONLY 53 | public void setId(long id) { 54 | this.id = id; 55 | } 56 | 57 | public String getName() { 58 | return name; 59 | } 60 | 61 | public void setName(String name) { 62 | this.name = name; 63 | } 64 | 65 | public String getDescription() { 66 | return description; 67 | } 68 | 69 | public void setDescription(String description) { 70 | this.description = description; 71 | } 72 | 73 | public int getRating() { 74 | return rating; 75 | } 76 | 77 | public void setRating(int rating) { 78 | this.rating = rating; 79 | } 80 | 81 | public String getCity() { 82 | return city; 83 | } 84 | 85 | public void setCity(String city) { 86 | this.city = city; 87 | } 88 | 89 | public Continent getContinent() { 90 | return continent; 91 | } 92 | 93 | public void setContinent(Continent continent) { 94 | this.continent = continent; 95 | } 96 | 97 | @OneToOne 98 | @JoinColumn(name = "building_id") 99 | private Building building; 100 | 101 | public Building getBuilding() { 102 | return building; 103 | } 104 | 105 | public void setBuilding(Building building) { 106 | this.building = building; 107 | } 108 | 109 | @Override 110 | public String toString() { 111 | return "Hotel {" + 112 | "id=" + id + 113 | ", name='" + name + '\'' + 114 | ", description='" + description + '\'' + 115 | ", city='" + city + '\'' + 116 | ", rating=" + rating + 117 | '}'; 118 | } 119 | 120 | @Override 121 | public int compareTo(Hotel other) { 122 | Comparator comparator = Comparator.comparing(Hotel::getName) 123 | .thenComparing(Hotel::getContinent) 124 | .thenComparing(Hotel::getCity) 125 | .thenComparing(Hotel::getRating); 126 | return comparator.compare(this, other); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/main/java/hawk/entity/Item.java: -------------------------------------------------------------------------------- 1 | package hawk.entity; 2 | 3 | import javax.persistence.Entity; 4 | import javax.persistence.GeneratedValue; 5 | import javax.persistence.GenerationType; 6 | import javax.persistence.Id; 7 | 8 | @Entity 9 | public class Item { 10 | 11 | @Id 12 | @GeneratedValue(strategy=GenerationType.AUTO) 13 | private Long id; 14 | private String name; 15 | private String description; 16 | 17 | protected Item() {} 18 | 19 | public Item(Long id, String name, String description) { 20 | this(name, description); 21 | this.id = id; 22 | } 23 | 24 | public Item(String name, String description) { 25 | this.name = name; 26 | this.description = description; 27 | } 28 | 29 | public Long getId() { 30 | return id; 31 | } 32 | 33 | public String getName() { 34 | return name; 35 | } 36 | 37 | public String getDescription() { 38 | return description; 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/hawk/entity/TenantSupport.java: -------------------------------------------------------------------------------- 1 | package hawk.entity; 2 | 3 | public interface TenantSupport { 4 | void setTenantId(String tenantId); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/hawk/entity/User.java: -------------------------------------------------------------------------------- 1 | package hawk.entity; 2 | 3 | import org.hibernate.annotations.Filter; 4 | import org.hibernate.annotations.FilterDef; 5 | import org.hibernate.annotations.ParamDef; 6 | 7 | import javax.persistence.Column; 8 | import javax.persistence.Entity; 9 | import javax.persistence.GeneratedValue; 10 | import javax.persistence.GenerationType; 11 | import javax.persistence.Id; 12 | import javax.persistence.Table; 13 | import java.io.Serializable; 14 | 15 | @Entity 16 | @Table(name = "usertb", schema = "public") 17 | @FilterDef(name = "tenantFilter", parameters = {@ParamDef(name = "tenantId", type = "string")}) 18 | @Filter(name = "tenantFilter", condition = "tenant_id = :tenantId") 19 | public class User implements TenantSupport, Serializable { 20 | private static final long serialVersionUID = -6986746375915710855L; 21 | @Id 22 | @GeneratedValue(strategy=GenerationType.AUTO) 23 | private Long id; 24 | private String name; 25 | private String description; 26 | @Column(name = "tenant_id") 27 | private String tenantId; 28 | 29 | protected User() {} 30 | 31 | public User(Long id, String name, String description, String tenantId) { 32 | this(name, description, tenantId); 33 | this.id = id; 34 | } 35 | 36 | public User(String name, String description, String tenantId) { 37 | this.name = name; 38 | this.description = description; 39 | this.tenantId = tenantId; 40 | } 41 | 42 | public Long getId() { 43 | return id; 44 | } 45 | 46 | public String getName() { 47 | return name; 48 | } 49 | 50 | public String getDescription() { 51 | return description; 52 | } 53 | 54 | public String getTenantId() { 55 | return tenantId; 56 | } 57 | 58 | 59 | @Override 60 | public void setTenantId(String tenantId) { 61 | this.tenantId = tenantId; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/hawk/form/Search.java: -------------------------------------------------------------------------------- 1 | package hawk.form; 2 | 3 | 4 | public class Search { 5 | 6 | private String searchText; 7 | 8 | public Search() { 9 | } 10 | 11 | public Search(String searchText) { 12 | this.searchText = searchText; 13 | } 14 | 15 | public String getSearchText() { 16 | return searchText; 17 | } 18 | 19 | public void setSearchText(String searchText) { 20 | this.searchText = searchText; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/hawk/hotel/dao/HotelRepository.java: -------------------------------------------------------------------------------- 1 | package hawk.hotel.dao; 2 | 3 | import hawk.hotel.domain.Hotel; 4 | import org.springframework.data.domain.Page; 5 | import org.springframework.data.domain.Pageable; 6 | import org.springframework.data.repository.PagingAndSortingRepository; 7 | import org.springframework.stereotype.Repository; 8 | 9 | /** 10 | * Repository can be used to delegate CRUD operations against the data source: http://goo.gl/P1J8QH 11 | */ 12 | @Repository 13 | public interface HotelRepository extends PagingAndSortingRepository { 14 | Hotel findHotelByCity(String city); 15 | 16 | Page findAll(Pageable pageable); 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/hawk/hotel/domain/Building.java: -------------------------------------------------------------------------------- 1 | package hawk.hotel.domain; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | 5 | import javax.persistence.*; 6 | import java.util.List; 7 | 8 | @Entity 9 | @Table(name = "building") 10 | public class Building { 11 | 12 | @Id 13 | @GeneratedValue() 14 | private long id; 15 | 16 | @Column(nullable = false) 17 | private String name; 18 | 19 | @OneToMany 20 | @JoinTable( 21 | name = "building_staff", 22 | joinColumns = @JoinColumn(name = "building_id"), 23 | inverseJoinColumns = @JoinColumn(name = "staff_id") 24 | ) 25 | private List staff; 26 | 27 | @Column(columnDefinition = "jsonb") 28 | private String blahs; 29 | 30 | public long getId() { 31 | return id; 32 | } 33 | 34 | public void setId(long id) { 35 | this.id = id; 36 | } 37 | 38 | public List getStaff() { 39 | return staff; 40 | } 41 | 42 | public void setStaff(List staff) { 43 | this.staff = staff; 44 | } 45 | 46 | public List getBlahs() { 47 | ObjectMapper objectMapper = new ObjectMapper(); 48 | try { 49 | return objectMapper.readValue(this.blahs, objectMapper.getTypeFactory().constructCollectionType(List.class, Integer.class)); 50 | } catch (Exception e) { 51 | throw new RuntimeException("Error converting JSON to numbers", e); 52 | } 53 | } 54 | 55 | public void setBlahs(List blahs) { 56 | ObjectMapper objectMapper = new ObjectMapper(); 57 | try { 58 | this.blahs = objectMapper.writeValueAsString(blahs); 59 | } catch (Exception e) { 60 | throw new RuntimeException("Error converting numbers to JSON", e); 61 | } 62 | } 63 | 64 | public String getName() { 65 | return name; 66 | } 67 | 68 | public void setName(String name) { 69 | this.name = name; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/hawk/hotel/domain/Continent.java: -------------------------------------------------------------------------------- 1 | package hawk.hotel.domain; 2 | 3 | public enum Continent { 4 | AFRICA, 5 | ANTARCTICA, 6 | ASIA, 7 | AUSTRALIA, 8 | EUROPE, 9 | NORTH_AMERICA, 10 | SOUTH_AMERICA 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/hawk/hotel/domain/Hotel.java: -------------------------------------------------------------------------------- 1 | package hawk.hotel.domain; 2 | 3 | import javax.persistence.*; 4 | import javax.xml.bind.annotation.XmlAccessType; 5 | import javax.xml.bind.annotation.XmlAccessorType; 6 | import javax.xml.bind.annotation.XmlRootElement; 7 | import java.util.Comparator; 8 | 9 | /* 10 | * a simple domain entity doubling as a DTO 11 | */ 12 | @Entity 13 | @Table(name = "hotel") 14 | @XmlRootElement 15 | @XmlAccessorType(XmlAccessType.FIELD) 16 | public class Hotel implements Comparable { 17 | 18 | @Column() 19 | String city; 20 | @Id 21 | @GeneratedValue() 22 | private long id; 23 | @Column(nullable = false) 24 | private String name; 25 | @Column() 26 | private String description; 27 | @Column() 28 | private int rating; 29 | 30 | @Column() 31 | @Enumerated(EnumType.STRING) 32 | private Continent continent; 33 | @OneToOne 34 | @JoinColumn(name = "building_id") 35 | private Building building; 36 | 37 | public Hotel() { 38 | } 39 | 40 | public Hotel(String name, String description, int rating) { 41 | this.name = name; 42 | this.description = description; 43 | this.rating = rating; 44 | } 45 | 46 | public long getId() { 47 | return this.id; 48 | } 49 | 50 | // for tests ONLY 51 | public void setId(long id) { 52 | this.id = id; 53 | } 54 | 55 | public String getName() { 56 | return name; 57 | } 58 | 59 | public void setName(String name) { 60 | this.name = name; 61 | } 62 | 63 | public String getDescription() { 64 | return description; 65 | } 66 | 67 | public void setDescription(String description) { 68 | this.description = description; 69 | } 70 | 71 | public int getRating() { 72 | return rating; 73 | } 74 | 75 | public void setRating(int rating) { 76 | this.rating = rating; 77 | } 78 | 79 | public String getCity() { 80 | return city; 81 | } 82 | 83 | public void setCity(String city) { 84 | this.city = city; 85 | } 86 | 87 | public Continent getContinent() { 88 | return continent; 89 | } 90 | 91 | public void setContinent(Continent continent) { 92 | this.continent = continent; 93 | } 94 | 95 | public Building getBuilding() { 96 | return building; 97 | } 98 | 99 | public void setBuilding(Building building) { 100 | this.building = building; 101 | } 102 | 103 | @Override 104 | public String toString() { 105 | return "Hotel {" + 106 | "id=" + id + 107 | ", name='" + name + '\'' + 108 | ", description='" + description + '\'' + 109 | ", city='" + city + '\'' + 110 | ", rating=" + rating + 111 | '}'; 112 | } 113 | 114 | @Override 115 | public int compareTo(Hotel other) { 116 | Comparator comparator = Comparator.comparing(Hotel::getName) 117 | .thenComparing(Hotel::getContinent) 118 | .thenComparing(Hotel::getCity) 119 | .thenComparing(Hotel::getRating); 120 | return comparator.compare(this, other); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/main/java/hawk/hotel/domain/RestErrorInfo.java: -------------------------------------------------------------------------------- 1 | package hawk.hotel.domain; 2 | 3 | import javax.xml.bind.annotation.XmlRootElement; 4 | 5 | /* 6 | * A sample class for adding error information in the response 7 | */ 8 | @XmlRootElement 9 | public class RestErrorInfo { 10 | public final String detail; 11 | public final String message; 12 | 13 | public RestErrorInfo(Exception ex, String detail) { 14 | this.message = ex.getLocalizedMessage(); 15 | this.detail = detail; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/hawk/hotel/domain/Staff.java: -------------------------------------------------------------------------------- 1 | package hawk.hotel.domain; 2 | 3 | import javax.persistence.*; 4 | 5 | @Entity 6 | @Table(name = "staff") 7 | public class Staff { 8 | @Id 9 | @GeneratedValue() 10 | private long id; 11 | 12 | @Column(nullable = false) 13 | private String firstName; 14 | 15 | @Column(nullable = false) 16 | private String lastName; 17 | 18 | @ManyToOne 19 | @JoinTable( 20 | name = "building_staff", 21 | joinColumns = @JoinColumn(name = "staff_id"), 22 | inverseJoinColumns = @JoinColumn(name = "building_id") 23 | ) 24 | private Building building; 25 | 26 | public long getId() { 27 | return id; 28 | } 29 | 30 | public void setId(long id) { 31 | this.id = id; 32 | } 33 | 34 | public String getFirstName() { 35 | return firstName; 36 | } 37 | 38 | public void setFirstName(String firstName) { 39 | this.firstName = firstName; 40 | } 41 | 42 | public String getLastName() { 43 | return lastName; 44 | } 45 | 46 | public void setLastName(String lastName) { 47 | this.lastName = lastName; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/hawk/hotel/exception/DataFormatException.java: -------------------------------------------------------------------------------- 1 | package hawk.hotel.exception; 2 | 3 | /** 4 | * for HTTP 400 errors 5 | */ 6 | public final class DataFormatException extends RuntimeException { 7 | public DataFormatException() { 8 | super(); 9 | } 10 | 11 | public DataFormatException(String message, Throwable cause) { 12 | super(message, cause); 13 | } 14 | 15 | public DataFormatException(String message) { 16 | super(message); 17 | } 18 | 19 | public DataFormatException(Throwable cause) { 20 | super(cause); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/hawk/hotel/exception/ResourceNotFoundException.java: -------------------------------------------------------------------------------- 1 | package hawk.hotel.exception; 2 | 3 | /** 4 | * For HTTP 404 errros 5 | */ 6 | public class ResourceNotFoundException extends RuntimeException { 7 | public ResourceNotFoundException() { 8 | super(); 9 | } 10 | 11 | public ResourceNotFoundException(String message, Throwable cause) { 12 | super(message, cause); 13 | } 14 | 15 | public ResourceNotFoundException(String message) { 16 | super(message); 17 | } 18 | 19 | public ResourceNotFoundException(Throwable cause) { 20 | super(cause); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/hawk/hotel/rest/AbstractRestHandler.java: -------------------------------------------------------------------------------- 1 | package hawk.hotel.rest; 2 | 3 | import hawk.hotel.domain.RestErrorInfo; 4 | import hawk.hotel.exception.DataFormatException; 5 | import hawk.hotel.exception.ResourceNotFoundException; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.context.ApplicationEventPublisher; 9 | import org.springframework.context.ApplicationEventPublisherAware; 10 | import org.springframework.http.HttpStatus; 11 | import org.springframework.web.bind.annotation.ExceptionHandler; 12 | import org.springframework.web.bind.annotation.ResponseBody; 13 | import org.springframework.web.bind.annotation.ResponseStatus; 14 | import org.springframework.web.context.request.WebRequest; 15 | 16 | import javax.servlet.http.HttpServletResponse; 17 | 18 | /** 19 | * This class is meant to be extended by all REST resource "controllers". 20 | * It contains exception mapping and other common REST API functionality 21 | */ 22 | //@ControllerAdvice? 23 | public abstract class AbstractRestHandler implements ApplicationEventPublisherAware { 24 | 25 | protected static final String DEFAULT_PAGE_SIZE = "100"; 26 | protected static final String DEFAULT_PAGE_NUM = "0"; 27 | protected final Logger log = LoggerFactory.getLogger(this.getClass()); 28 | protected ApplicationEventPublisher eventPublisher; 29 | 30 | //todo: replace with exception mapping 31 | public static T checkResourceFound(final T resource) { 32 | if (resource == null) { 33 | throw new ResourceNotFoundException("resource not found"); 34 | } 35 | return resource; 36 | } 37 | 38 | @ResponseStatus(HttpStatus.BAD_REQUEST) 39 | @ExceptionHandler(DataFormatException.class) 40 | public 41 | @ResponseBody 42 | RestErrorInfo handleDataStoreException(DataFormatException ex, WebRequest request, HttpServletResponse response) { 43 | log.info("Converting Data Store exception to RestResponse : " + ex.getMessage()); 44 | 45 | return new RestErrorInfo(ex, "You messed up."); 46 | } 47 | 48 | @ResponseStatus(HttpStatus.NOT_FOUND) 49 | @ExceptionHandler(ResourceNotFoundException.class) 50 | public 51 | @ResponseBody 52 | RestErrorInfo handleResourceNotFoundException(ResourceNotFoundException ex, WebRequest request, HttpServletResponse response) { 53 | log.info("ResourceNotFoundException handler:" + ex.getMessage()); 54 | 55 | return new RestErrorInfo(ex, "Sorry I couldn't find it."); 56 | } 57 | 58 | @Override 59 | public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { 60 | this.eventPublisher = applicationEventPublisher; 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/hawk/hotel/rest/HotelController.java: -------------------------------------------------------------------------------- 1 | package hawk.hotel.rest; 2 | 3 | import hawk.hotel.domain.Continent; 4 | import hawk.hotel.domain.Hotel; 5 | import hawk.hotel.exception.DataFormatException; 6 | import hawk.hotel.service.HotelService; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.data.domain.Page; 9 | import org.springframework.http.HttpStatus; 10 | import org.springframework.http.MediaType; 11 | import org.springframework.http.ResponseEntity; 12 | import org.springframework.web.bind.annotation.*; 13 | 14 | import javax.servlet.http.HttpServletRequest; 15 | import javax.servlet.http.HttpServletResponse; 16 | import java.util.List; 17 | import java.util.Map; 18 | 19 | /* 20 | * Demonstrates how to set up RESTful API endpoints using Spring MVC 21 | */ 22 | 23 | @RestController 24 | @RequestMapping(value = "/example/v1/hotels") 25 | public class HotelController extends AbstractRestHandler { 26 | 27 | @Autowired 28 | private HotelService hotelService; 29 | 30 | @RequestMapping(value = "", 31 | method = RequestMethod.POST, 32 | consumes = {"application/json", "application/xml"}, 33 | produces = {"application/json", "application/xml"}) 34 | @ResponseStatus(HttpStatus.CREATED) 35 | public void createHotel(@RequestBody Hotel hotel, 36 | HttpServletRequest request, HttpServletResponse response) { 37 | Hotel createdHotel = this.hotelService.createHotel(hotel); 38 | response.setHeader("Location", request.getRequestURL().append("/").append(createdHotel.getId()).toString()); 39 | } 40 | 41 | @RequestMapping(value = "", 42 | method = RequestMethod.GET, 43 | produces = {"application/json", "application/xml"}) 44 | @ResponseStatus(HttpStatus.OK) 45 | public 46 | @ResponseBody 47 | Page getAllHotel(@RequestParam(value = "page", required = true, defaultValue = DEFAULT_PAGE_NUM) Integer page, 48 | @RequestParam(value = "size", required = true, defaultValue = DEFAULT_PAGE_SIZE) Integer size, 49 | HttpServletRequest request, HttpServletResponse response) { 50 | return this.hotelService.getAllHotels(page, size); 51 | } 52 | 53 | @RequestMapping(value = "/upper-bounded-list", 54 | method = RequestMethod.GET, 55 | produces = {"application/json", "application/xml"}) 56 | @ResponseStatus(HttpStatus.OK) 57 | public 58 | @ResponseBody 59 | Page upperBoundedCollectionOfHotels( 60 | @RequestParam(value = "page", required = true, defaultValue = DEFAULT_PAGE_NUM) Integer page, 61 | @RequestParam(value = "size", required = true, defaultValue = DEFAULT_PAGE_SIZE) Integer size, 62 | HttpServletRequest request, HttpServletResponse response) { 63 | return this.hotelService.getAllHotels(page, size); 64 | } 65 | 66 | @RequestMapping(value = "/lower-bounded-list", 67 | method = RequestMethod.GET, 68 | produces = {"application/json", "application/xml"}) 69 | @ResponseStatus(HttpStatus.OK) 70 | public 71 | @ResponseBody 72 | Page lowerBoundedCollectionOfHotels( 73 | @RequestParam(value = "page", required = true, defaultValue = DEFAULT_PAGE_NUM) Integer page, 74 | 75 | @RequestParam(value = "size", required = true, defaultValue = DEFAULT_PAGE_SIZE) Integer size, 76 | HttpServletRequest request, HttpServletResponse response) { 77 | return this.hotelService.getAllHotels(page, size); 78 | } 79 | 80 | @RequestMapping(value = "/unbounded-list", 81 | method = RequestMethod.GET, 82 | produces = {"application/json", "application/xml"}) 83 | @ResponseStatus(HttpStatus.OK) 84 | public 85 | @ResponseBody 86 | Page unboundedCollectionOfHotels( 87 | @RequestParam(value = "page", required = true, defaultValue = DEFAULT_PAGE_NUM) Integer page, 88 | 89 | @RequestParam(value = "size", required = true, defaultValue = DEFAULT_PAGE_SIZE) Integer size, 90 | HttpServletRequest request, HttpServletResponse response) { 91 | return this.hotelService.getAllHotels(page, size); 92 | } 93 | 94 | @RequestMapping(value = "/random", 95 | method = RequestMethod.GET, 96 | produces = {"application/json", "application/xml"}) 97 | @ResponseStatus(HttpStatus.OK) 98 | public @ResponseBody ResponseEntity getRandomHotel( 99 | @RequestParam(required = false, defaultValue = "null") Continent continent, 100 | HttpServletRequest request, HttpServletResponse response 101 | ) { 102 | System.out.println("Received a continent to filter by... " + continent.name() + " ...ignoring..."); 103 | return ResponseEntity.ok(this.hotelService.randomHotel()); 104 | } 105 | 106 | @RequestMapping(value = "/locations", 107 | method = RequestMethod.GET, 108 | produces = {"application/json", "application/xml"}) 109 | @ResponseStatus(HttpStatus.OK) 110 | public @ResponseBody Map> getHotelsByLocation(HttpServletRequest request, HttpServletResponse response) { 111 | return this.hotelService.hotelsByLocation(); 112 | } 113 | 114 | @RequestMapping(value = "/{id}", 115 | method = RequestMethod.GET, 116 | produces = {"application/json", "application/xml"}) 117 | @ResponseStatus(HttpStatus.OK) 118 | public 119 | @ResponseBody 120 | Hotel getHotel( 121 | @PathVariable("id") Long id, 122 | HttpServletRequest request, HttpServletResponse response) throws Exception { 123 | Hotel hotel = this.hotelService.getHotel(id); 124 | checkResourceFound(hotel); 125 | //todo: http://goo.gl/6iNAkz 126 | return hotel; 127 | } 128 | 129 | @RequestMapping(value = "/{id}", 130 | method = RequestMethod.PUT, 131 | consumes = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE}, 132 | produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE}) 133 | @ResponseStatus(HttpStatus.NO_CONTENT) 134 | public void updateHotel( 135 | @PathVariable("id") Long id, @RequestBody Hotel hotel, 136 | HttpServletRequest request, HttpServletResponse response) { 137 | checkResourceFound(this.hotelService.getHotel(id)); 138 | if (id != hotel.getId()) throw new DataFormatException("ID doesn't match!"); 139 | this.hotelService.updateHotel(hotel); 140 | } 141 | 142 | //todo: @ApiImplicitParams, @ApiResponses 143 | @RequestMapping(value = "/{id}", 144 | method = RequestMethod.DELETE, 145 | produces = {"application/json", "application/xml"}) 146 | @ResponseStatus(HttpStatus.NO_CONTENT) 147 | public void deleteHotel( 148 | @PathVariable("id") Long id, HttpServletRequest request, 149 | HttpServletResponse response 150 | ) { 151 | checkResourceFound(this.hotelService.getHotel(id)); 152 | this.hotelService.deleteHotel(id); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/main/java/hawk/hotel/service/HotelService.java: -------------------------------------------------------------------------------- 1 | package hawk.hotel.service; 2 | 3 | import hawk.hotel.dao.HotelRepository; 4 | import hawk.hotel.domain.Continent; 5 | import hawk.hotel.domain.Hotel; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.data.domain.Page; 10 | import org.springframework.data.domain.PageRequest; 11 | import org.springframework.data.domain.Pageable; 12 | import org.springframework.data.domain.Sort; 13 | import org.springframework.stereotype.Service; 14 | 15 | import java.util.*; 16 | 17 | /* 18 | * Sample service to demonstrate what the API would use to get things done 19 | */ 20 | @Service 21 | public class HotelService { 22 | 23 | private static final Logger log = LoggerFactory.getLogger(HotelService.class); 24 | 25 | @Autowired 26 | private HotelRepository hotelRepository; 27 | 28 | public HotelService() { 29 | } 30 | 31 | public Hotel createHotel(Hotel hotel) { 32 | return hotelRepository.save(hotel); 33 | } 34 | 35 | public Hotel getHotel(long id) { 36 | return hotelRepository.findById(id).orElse(null); 37 | } 38 | 39 | public void updateHotel(Hotel hotel) { 40 | hotelRepository.save(hotel); 41 | } 42 | 43 | public void deleteHotel(Long id) { 44 | hotelRepository.deleteById(id); 45 | } 46 | 47 | public Page getAllHotels(Integer page, Integer size) { 48 | return hotelRepository.findAll(PageRequest.of(page, size)); 49 | } 50 | 51 | public Hotel randomHotel() { 52 | Pageable pageable = PageRequest.of(0, Integer.MAX_VALUE); 53 | List hotels = new ArrayList<>(hotelRepository.findAll(pageable).getContent()); 54 | Collections.shuffle(hotels); 55 | return hotels.isEmpty() ? null : hotels.get(0); 56 | } 57 | 58 | public Map> hotelsByLocation() { 59 | Pageable pageable = PageRequest.of(0, Integer.MAX_VALUE, Sort.unsorted()); 60 | List hotels = new ArrayList<>(hotelRepository.findAll(pageable).getContent()); 61 | Collections.shuffle(hotels); 62 | 63 | Map> hotelsByLocation = new HashMap<>(); 64 | 65 | for (Hotel hotel : hotels) { 66 | Continent[] continents = Continent.values(); 67 | String rnd = hotel.getName() + hotel.getCity() + hotel.getDescription() + hotel.getId(); 68 | int i = rnd.length() % continents.length; 69 | Continent continent = continents[i]; 70 | 71 | hotelsByLocation.computeIfAbsent(continent, k -> new ArrayList<>()); 72 | hotelsByLocation.get(continent).add(hotel); 73 | } 74 | return hotelsByLocation; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/hawk/interceptor/DBInterceptor.java: -------------------------------------------------------------------------------- 1 | package hawk.interceptor; 2 | 3 | import hawk.context.TenantContext; 4 | import hawk.entity.TenantSupport; 5 | import org.hibernate.EmptyInterceptor; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties; 8 | import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder; 9 | import org.springframework.context.annotation.Bean; 10 | import org.springframework.context.annotation.Configuration; 11 | import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; 12 | import org.hibernate.type.Type; 13 | 14 | import javax.sql.DataSource; 15 | import java.io.Serializable; 16 | import java.util.HashMap; 17 | import java.util.Map; 18 | 19 | @Configuration 20 | public class DBInterceptor { 21 | 22 | @Autowired 23 | private JpaProperties jpaProperties; 24 | 25 | @Bean 26 | public EmptyInterceptor hibernateInterceptor() { 27 | return new EmptyInterceptor() { 28 | 29 | @Override 30 | public void onDelete(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) { 31 | if (entity instanceof TenantSupport) { 32 | ((TenantSupport) entity).setTenantId(TenantContext.getCurrentTenant()); 33 | } 34 | } 35 | 36 | 37 | @Override 38 | public boolean onFlushDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState, String[] propertyNames, Type[] types) { 39 | if (entity instanceof TenantSupport) { 40 | ((TenantSupport) entity).setTenantId(TenantContext.getCurrentTenant()); 41 | } 42 | return false; 43 | } 44 | 45 | @Override 46 | public boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) { 47 | if (entity instanceof TenantSupport) { 48 | ((TenantSupport) entity).setTenantId(TenantContext.getCurrentTenant()); 49 | } 50 | return false; 51 | } 52 | }; 53 | } 54 | 55 | @Bean 56 | public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder factory, DataSource dataSource, JpaProperties properties) { 57 | Map jpaPropertiesMap = new HashMap<>(jpaProperties.getProperties()); 58 | jpaPropertiesMap.put("hibernate.ejb.interceptor", hibernateInterceptor()); 59 | return factory.dataSource(dataSource) 60 | // This replaces the traditional @EntityScan that you'd find associated with @EnableJpaRepositories 61 | .packages("hawk.entity", "hawk.hotel.domain") 62 | .properties(jpaPropertiesMap) 63 | .build(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/hawk/repos/ItemRepo.java: -------------------------------------------------------------------------------- 1 | package hawk.repos; 2 | 3 | import hawk.entity.Item; 4 | import hawk.form.Search; 5 | import org.springframework.data.repository.CrudRepository; 6 | import org.springframework.data.repository.Repository; 7 | 8 | import java.util.List; 9 | 10 | public interface ItemRepo extends CrudRepository { 11 | 12 | List findByNameOrDescription(String name, String description); 13 | 14 | } 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/main/java/hawk/repos/ItemsRepo.java: -------------------------------------------------------------------------------- 1 | package hawk.repos; 2 | 3 | import hawk.entity.Item; 4 | import org.springframework.data.repository.Repository; 5 | 6 | import java.util.List; 7 | 8 | public interface ItemsRepo extends Repository { 9 | List findByNameContainingOrDescriptionContaining(String name, String description); 10 | 11 | Item findById(Long id); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/hawk/repos/UserRepo.java: -------------------------------------------------------------------------------- 1 | package hawk.repos; 2 | 3 | import hawk.entity.User; 4 | import org.springframework.data.repository.CrudRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | import java.util.List; 8 | 9 | @Repository 10 | public interface UserRepo extends CrudRepository { 11 | User findByName(String name); 12 | List findAllByNameIsLike(String name); 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/hawk/service/SearchService.java: -------------------------------------------------------------------------------- 1 | package hawk.service; 2 | 3 | import hawk.entity.Item; 4 | import hawk.form.Search; 5 | import org.hibernate.Session; 6 | import org.hibernate.jdbc.ReturningWork; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import javax.persistence.EntityManager; 9 | import java.sql.Connection; 10 | import java.sql.ResultSet; 11 | import java.sql.PreparedStatement; 12 | import java.sql.SQLException; 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | import java.util.logging.Level; 16 | import java.util.logging.Logger; 17 | 18 | 19 | public class SearchService { 20 | 21 | private static final Logger LOGGER = Logger.getLogger(SearchService.class.getName()); 22 | 23 | @Autowired 24 | EntityManager entityManager; 25 | 26 | public List search(Search search) { 27 | final Session session = (Session) entityManager.unwrap(Session.class); 28 | return session.doReturningWork(new ReturningWork>() { 29 | @Override 30 | public List execute(Connection connection) throws SQLException { 31 | List items = new ArrayList<>(); 32 | // The wrong way 33 | String query = "select id, name, description from ITEM where description like '%" + 34 | search.getSearchText() + "%'"; 35 | 36 | LOGGER.log(Level.INFO, "SQL Query: {0}", query);; 37 | ResultSet rs = connection 38 | .createStatement() 39 | .executeQuery(query); 40 | 41 | /* The righter way, should probably use built in Data Model for this, but this is safe 42 | String query = "select id, name, description from ITEM where description like ?"; 43 | PreparedStatement statement = connection.prepareStatement(query); 44 | statement.setString(1, "%" + search.getSearchText() + "%"); 45 | LOGGER.log(Level.INFO, "SQL Query {0}", statement); 46 | ResultSet rs = statement.executeQuery(); 47 | */ 48 | 49 | while (rs.next()) { 50 | items.add(new Item(rs.getLong("id"), rs.getString("name"), rs.getString("description"))); 51 | } 52 | rs.close(); 53 | return items; 54 | } 55 | }); 56 | 57 | } 58 | 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/hawk/service/UserSearchService.java: -------------------------------------------------------------------------------- 1 | package hawk.service; 2 | 3 | import hawk.entity.User; 4 | import hawk.form.Search; 5 | import hawk.repos.UserRepo; 6 | import org.hibernate.Session; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | 9 | import javax.persistence.EntityManager; 10 | import java.sql.ResultSet; 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | import java.util.logging.Level; 14 | import java.util.logging.Logger; 15 | 16 | 17 | public class UserSearchService { 18 | 19 | private static final Logger LOGGER = Logger.getLogger(UserSearchService.class.getName()); 20 | 21 | @Autowired 22 | public 23 | EntityManager entityManager; 24 | 25 | public List search(Search search) { 26 | final Session session = entityManager.unwrap(Session.class); 27 | return session.doReturningWork(connection -> { 28 | List users1 = new ArrayList<>(); 29 | // The wrong way 30 | String query = "select id, name, description, tenant_id from public.usertb where name like '%" + 31 | search.getSearchText() + "%'"; 32 | 33 | LOGGER.log(Level.INFO, "SQL Query {0}", query); 34 | ResultSet rs = connection 35 | .createStatement() 36 | .executeQuery(query); 37 | 38 | /* The righter way, should probably use built in Data Model for this, but this is safe 39 | String query = "select id, name, description from ITEM where description like ?"; 40 | PreparedStatement statement = connection.prepareStatement(query); 41 | statement.setString(1, "%" + search.getSearchText() + "%"); 42 | LOGGER.log(Level.INFO, "SQL Query {0}", statement); 43 | ResultSet rs = statement.executeQuery(); 44 | */ 45 | 46 | while (rs.next()) { 47 | users1.add(new User(rs.getLong("id"), rs.getString("name"), rs.getString("description"), rs.getString("tenant_id"))); 48 | } 49 | rs.close(); 50 | return users1; 51 | }); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/hawk/service/UserService.java: -------------------------------------------------------------------------------- 1 | package hawk.service; 2 | 3 | import hawk.entity.User; 4 | import hawk.form.Search; 5 | import hawk.repos.UserRepo; 6 | import org.hibernate.Session; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.stereotype.Service; 9 | 10 | import javax.persistence.EntityManager; 11 | import java.sql.ResultSet; 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | import java.util.logging.Level; 15 | import java.util.logging.Logger; 16 | 17 | public class UserService { 18 | private static final Logger LOGGER = Logger.getLogger(UserSearchService.class.getName()); 19 | 20 | @Autowired 21 | public 22 | EntityManager entityManager; 23 | 24 | @Autowired 25 | private UserRepo userRepo; 26 | 27 | public User findUser(String name) { 28 | return this.userRepo.findByName(name); 29 | } 30 | 31 | public List findUsersByName(String name) { return this.userRepo.findAllByNameIsLike('%' + name + '%'); } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/resources/application-postgresql.yaml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 9000 3 | ssl: 4 | enabled: true 5 | key-store: classpath:javavulny.p12 6 | key-store-password: stackhawk 7 | key-alias: JavaVulny 8 | error: 9 | whitelabel: 10 | enabled: false 11 | include-stacktrace: always 12 | 13 | spring: 14 | datasource: 15 | url: jdbc:postgresql://localhost:5432/postgres 16 | platform: postgres 17 | username: postgresql 18 | password: postgresql 19 | driver-class-name: org.postgresql.Driver 20 | jpa: 21 | database: POSTGRESQL 22 | generate-ddl: true 23 | hibernate: 24 | ddl-auto: create-drop 25 | properties: 26 | hibernate.jdbc.lob.non_contextual_creation: true 27 | hibernate.use_sql_comments: false 28 | hibernate.dialect: org.hibernate.dialect.PostgreSQL81Dialect 29 | show-sql: false 30 | 31 | springdoc: 32 | api-docs: 33 | path: /openapi 34 | 35 | logging: 36 | level: 37 | org.hibernate.SQL: debug 38 | org.hibernate.type: trace 39 | org.hibernate.descriptor.sql: trace 40 | org.springframework: info 41 | org.baeldung: info 42 | 43 | management: 44 | endpoints: 45 | jmx.exposure.include: '*' 46 | web.exposure.include: '*' 47 | 48 | payload: 49 | count: 50 50 | start-size: 10240 51 | delayStart: 0 52 | delayEnd: 0 -------------------------------------------------------------------------------- /src/main/resources/application-windows.yaml: -------------------------------------------------------------------------------- 1 | 2 | spring: 3 | datasource: 4 | url: jdbc:h2:file:.\\db\\vulny;DB_CLOSE_ON_EXIT=FALSE;AUTO_RECONNECT=TRUE;MODE=PostgreSQL; 5 | -------------------------------------------------------------------------------- /src/main/resources/application.yaml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 9000 3 | ssl: 4 | enabled: true 5 | key-store: classpath:javavulny.p12 6 | key-store-password: stackhawk 7 | key-alias: JavaVulny 8 | error: 9 | whitelabel: 10 | enabled: false 11 | include-stacktrace: always 12 | 13 | spring: 14 | datasource: 15 | url: jdbc:h2:file:${PWD}/db/vulny;DB_CLOSE_ON_EXIT=FALSE;AUTO_RECONNECT=TRUE;MODE=PostgreSQL; 16 | username: sa 17 | password: password 18 | driver-class-name: org.h2.Driver 19 | jpa: 20 | database-platform: org.hibernate.dialect.H2Dialect 21 | generate-ddl: true 22 | hibernate: 23 | ddl-auto: create-drop 24 | properties: 25 | hibernate.jdbc.lob.non_contextual_creation: true 26 | 27 | springdoc: 28 | api-docs: 29 | path: /openapi 30 | 31 | payload: 32 | start-size: 3096 33 | count: 10 34 | delayStart: 0 35 | delayEnd: 0 36 | -------------------------------------------------------------------------------- /src/main/resources/javavulny.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDpjCCAo4CCQCTSuQL2NUQ5DANBgkqhkiG9w0BAQsFADCBlDELMAkGA1UEBhMC 3 | VVMxETAPBgNVBAgMCENvbG9yYWRvMQ8wDQYDVQQHDAZEZW52ZXIxEjAQBgNVBAoM 4 | CVN0YWNrSGF3azEUMBIGA1UECwwLRW5naW5lZXJpbmcxEjAQBgNVBAMMCWxvY2Fs 5 | aG9zdDEjMCEGCSqGSIb3DQEJARYUZGV2b3BzQHN0YWNraGF3ay5jb20wHhcNMjEw 6 | NTI1MDExMTAwWhcNMzEwNTIzMDExMTAwWjCBlDELMAkGA1UEBhMCVVMxETAPBgNV 7 | BAgMCENvbG9yYWRvMQ8wDQYDVQQHDAZEZW52ZXIxEjAQBgNVBAoMCVN0YWNrSGF3 8 | azEUMBIGA1UECwwLRW5naW5lZXJpbmcxEjAQBgNVBAMMCWxvY2FsaG9zdDEjMCEG 9 | CSqGSIb3DQEJARYUZGV2b3BzQHN0YWNraGF3ay5jb20wggEiMA0GCSqGSIb3DQEB 10 | AQUAA4IBDwAwggEKAoIBAQDFQb/SgpIRvw7x9UKagiLqZkjjmFqaR/UtH4KHN90Z 11 | UrHUcCDVyIvhgcAknEg4+jNAExw1MwYcy4ekE+acWwspPb7sDP9MDowQs3LaiyCY 12 | 0ikRXp8EO/fRDEav64s4+bRk2kdA0KVgQNzQ5ipiHbZe4jYAuV2pVoOYG+s2cMtu 13 | UjH9kL1lHjJOGSQW95BzviriDmNRP7VAyl/t7fsFV3VBF+Pi46X5+cros3L38Dqg 14 | EZfbWiNPhuYlWJkhlbM4I54v/gzFXmgt+ZWLPvTIyibzr/bIzy3qZUFPzMvWIppJ 15 | auCtKXKlfoLHijU8ja0V5uOOddmfYIiI9XRuNfijUOT3AgMBAAEwDQYJKoZIhvcN 16 | AQELBQADggEBAI7rBKaWv3u8FO3ne0n7Pe2v5JnL8pYJiPneRsjOGlbmdTuYgaaz 17 | jiPOOA5ca/KIwk+YjXV1E9hl19oevUWi1RskZTnWd5IlI6IOjoZKVoL8igELDOGU 18 | 3yG8RHieAmY7QH+moNfLTcJFJ/jrEm9TJWzTADZ09eIn69AuANQ8S8CYPbzhE1+S 19 | H2yX61l7doSJTi0/b1igBjw+5+UJdTkJ9xxmz1Ky3nPL1VXBymGzH5/9Kz98qrj1 20 | myXF4IjTMXOnkPjotf02ZrVFWBLXtacFVzwkNygMP/uELX8Wa4+OYTilwSQ6vF03 21 | q6MPAlmTzpPReOO+Eaqg3rblA/MjG3h/MHk= 22 | -----END CERTIFICATE----- 23 | -------------------------------------------------------------------------------- /src/main/resources/javavulny.p12: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaakaww/javaspringvulny/b50a7ae704f1f9c431adb74d3e7e46c7c7b4e2a2/src/main/resources/javavulny.p12 -------------------------------------------------------------------------------- /src/main/resources/keyStore.pem: -------------------------------------------------------------------------------- 1 | Bag Attributes 2 | friendlyName: javavulny 3 | localKeyID: 54 69 6D 65 20 31 35 37 33 37 34 38 34 34 38 38 33 31 4 | Key Attributes: 5 | -----BEGIN PRIVATE KEY----- 6 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCzhWraqN+mJQuJ 7 | +JKBb4tcwJ1m5lTYccZbvsvCkCdQF0Ge0PhqYK8I9XyFTMPDv88h1Q+qDVlkqV5v 8 | VKFeR6v/REOH0AFJ4qC/mmhb9h3LnvyU6KtsZ4KWUfNfGhbpYxVTLU98CzqO9VTN 9 | xVCreXkTWCpbFTHGyb31sNG3xobL5PLgtNPECaDTJeiY0Z2ID/6fARWv4zn0luQS 10 | gDRLE2K73i2YjgD05OwdNRHklfnQKZLdkXX/TMVqzNT0Hg9a/thBQAIuozwK1SVs 11 | Ofry/xFGWii19nDR4PaYjT0Xt/BQIt6tk4rg2Xt1XrTRxzrdzSIpHPQoO9g1nl9F 12 | /yoaq87VAgMBAAECggEBAK47UMfDmrxvu9E+lUJWdhWhQ6L47WPcmL22YaAFtIIA 13 | 8EkpW9Ay4e56A06EgINdh7S4SqmqpxYRY3v90Vfli8MGB9yqPnXtJEKRo7ONHMHh 14 | HheP8cAWfiFSjDfC5HXDGpFNqpUjeMQCyzmixXaHQMFLkmAwI81hQ6JO6FvKrEz5 15 | 2WCja/OyBwlAybpZgzKTkKGXSBY8NrcWfm8QEZHaJkDDw0aBhgANB2+He6dJCfyM 16 | 1NqauTvbTjeYb5XXQffMhCbYmZM0+f/R4vF3N39lGkcaHMCh9r7c1+lsUKT4oT6p 17 | YRwvUP3uzBfKIUJJCqw4PD30JDcT+CxMnXn27KLu0cECgYEA3zKxAfOAySz7vVmV 18 | nIc8p7q09QGOYkx1m3+gAyCJqUAUX1ypPw+C3rk9zAThnZYsJL//5daxw8FMXCSm 19 | JkcMB2Sj0FKhiqIi+LEgu0tni4COVaTTCG27IXs9sgZTyPGQIwjoT3NhB1Bt3Ksi 20 | h7hnv8fHDPXzv7ptYpwfyRWDZpkCgYEAzed8S37GeZGbW5qqDyuKnWjG/UPPeTrw 21 | 5DjCSyBVEF3kZrMSc+O2bgf87VFOHBTORzr5r0zNWy12r9w3Ky02z4cxeujulihz 22 | 9ObY6aim6iRHwYpB0D5Eqs4R6mK189bKb9OD/F7USGy//tPP7hqfQC1JHJed4fwR 23 | 0esIOFJ5250CgYAKwVCSPX+ZMhe3WhfBgSrFElQh8j9AMVsBcTbur6SyiSnyJm7p 24 | YP/3Z4m4yQtuIoxiAPZ9xsiuHCRn1ERFV+sWeB0Ertal7YYt4asoG0wUBs/VBKo+ 25 | n7sCtAOXDoL/DdTaO1WmbnLgfVmt5nk1fQdS0HehAkEKnhsjQ06sy+KXEQKBgBuU 26 | 9b57NgUU7zW/KyarE39wiK9mhQVdUuWPgN9lbl+p5C2psUAa8dYdddEoggka9GLw 27 | UIAeDqg3F94I5s7+2IfGOWeYilVNB/N3dPkf4XQd9grEBXrqzHIA9r9qB6upSybd 28 | s6F75n3pK3LsVr8zi7+iEVgmKz0Zch2nmUtKKQ61AoGAPHVizunifBjtKaQHFBdr 29 | p5llidfBU6S0DsdRBG2PYxHB4TMc16gYpZicCIOsTavjS++ZBdtcL1VFiutavKop 30 | JhZbwH6PR76GJbdwUS3pl8s5+w21KtMxOwR8Oqoc/Sr+Bno37Yek/5CaVwnysEd0 31 | GWwVDi+xxRJnUIsVzdUBP+8= 32 | -----END PRIVATE KEY----- 33 | Bag Attributes 34 | friendlyName: javavulny 35 | localKeyID: 54 69 6D 65 20 31 35 37 33 37 34 38 34 34 38 38 33 31 36 | subject=/C=US/ST=Colorado/L=Denver/O=StackHawk/OU=Engineering/CN=Topher Lamey 37 | issuer=/C=US/ST=Colorado/L=Denver/O=StackHawk/OU=Engineering/CN=Topher Lamey 38 | -----BEGIN CERTIFICATE----- 39 | MIIDgzCCAmugAwIBAgIEGBmMtTANBgkqhkiG9w0BAQsFADByMQswCQYDVQQGEwJV 40 | UzERMA8GA1UECBMIQ29sb3JhZG8xDzANBgNVBAcTBkRlbnZlcjESMBAGA1UEChMJ 41 | U3RhY2tIYXdrMRQwEgYDVQQLEwtFbmdpbmVlcmluZzEVMBMGA1UEAxMMVG9waGVy 42 | IExhbWV5MB4XDTE5MTExNDE2MjA0OFoXDTI5MTExMTE2MjA0OFowcjELMAkGA1UE 43 | BhMCVVMxETAPBgNVBAgTCENvbG9yYWRvMQ8wDQYDVQQHEwZEZW52ZXIxEjAQBgNV 44 | BAoTCVN0YWNrSGF3azEUMBIGA1UECxMLRW5naW5lZXJpbmcxFTATBgNVBAMTDFRv 45 | cGhlciBMYW1leTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALOFatqo 46 | 36YlC4n4koFvi1zAnWbmVNhxxlu+y8KQJ1AXQZ7Q+Gpgrwj1fIVMw8O/zyHVD6oN 47 | WWSpXm9UoV5Hq/9EQ4fQAUnioL+aaFv2Hcue/JToq2xngpZR818aFuljFVMtT3wL 48 | Oo71VM3FUKt5eRNYKlsVMcbJvfWw0bfGhsvk8uC008QJoNMl6JjRnYgP/p8BFa/j 49 | OfSW5BKANEsTYrveLZiOAPTk7B01EeSV+dApkt2Rdf9MxWrM1PQeD1r+2EFAAi6j 50 | PArVJWw5+vL/EUZaKLX2cNHg9piNPRe38FAi3q2TiuDZe3VetNHHOt3NIikc9Cg7 51 | 2DWeX0X/KhqrztUCAwEAAaMhMB8wHQYDVR0OBBYEFA0Q5Gs6C1N+KexheGkzZCKU 52 | /b/oMA0GCSqGSIb3DQEBCwUAA4IBAQBM2P43w0qQ3ssYidzqHCVzdF2MRHd8ZaAz 53 | oMrkr+9FyeOrm0wImbpTCKNCGnm6jlXf1gMCmBFwnb45MdUcP74A3k1j8uSXHAjo 54 | J4Td5RyfDM8KaJwQJklfHCgixZn+Qmweyd9YV4NLTGETGmqnEMbG+98IICNkJtDw 55 | EXGUF+iUJmzofnrrOKkr2XSjyEoNA10bNt2gVMvquIQ5D2gcnOWf8ywNQxAFpNX2 56 | tKlV5ONh6wOuy0Ck1ykMdecsI3KFwxG6+Q9OMDiypT/l0m1h44uO9jBRI380myPS 57 | Ykr1fxXKDT3vG3+cF3UztkbNXAPAI5DTqP+iVBpSE9ImDCqu1Y32 58 | -----END CERTIFICATE----- 59 | -------------------------------------------------------------------------------- /src/main/resources/keystore.p12: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaakaww/javaspringvulny/b50a7ae704f1f9c431adb74d3e7e46c7c7b4e2a2/src/main/resources/keystore.p12 -------------------------------------------------------------------------------- /src/main/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | log4j.rootCategory=debug,console 2 | 3 | log4j.appender.console=org.apache.log4j.ConsoleAppender 4 | log4j.appender.console.target=System.out 5 | log4j.appender.console.immediateFlush=true 6 | log4j.appender.console.encoding=UTF-8 7 | #log4j.appender.console.threshold=warn 8 | 9 | log4j.appender.console.layout=org.apache.log4j.PatternLayout 10 | log4j.appender.console.layout.conversionPattern=%d [%t] %-5p %c - %m%n -------------------------------------------------------------------------------- /src/main/resources/templates/admin.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 15 | 16 | 17 | 18 |
19 |
20 |
21 | 25 |
26 |
27 |
28 | 31 |
32 |
33 |
34 |
35 | 38 |
39 |
40 |
41 |
42 | 45 |
46 |
47 | 48 |
49 | 50 | -------------------------------------------------------------------------------- /src/main/resources/templates/basic-auth.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 15 | 16 | 17 | 18 |
19 |
20 |
21 |
22 |
23 |
Token Header
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
Item Search:
37 | 38 | 39 |

40 |             
41 |
42 |
43 | 44 | 71 |
72 | 73 | -------------------------------------------------------------------------------- /src/main/resources/templates/companies.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 15 | 16 | 17 | 18 |
19 |
20 |
21 |
22 |
    23 |
  • Company A
  • 24 |
  • Company B
  • 25 |
  • Company C
  • 26 |
27 |
28 |
29 | 30 |
31 | 32 | -------------------------------------------------------------------------------- /src/main/resources/templates/general.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | StackHawk 6 | 7 | 8 | 10 | 19 | 20 | 21 | 22 | 23 |
24 |
25 | 28 | 29 |
30 |
31 | 32 |
33 |
34 |
35 |
36 | 37 | 38 | 40 | 43 | 46 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /src/main/resources/templates/hidden.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 |
13 |
14 | 17 |
18 |
19 | 20 |
21 | 22 | -------------------------------------------------------------------------------- /src/main/resources/templates/hidden2.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 16 | 17 | 18 | 19 |
20 |
21 |
22 |
23 |
    24 |
  • Just another hidden page
  • 25 |
26 |
27 |
28 | 29 |
30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/main/resources/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 |
13 | 17 |
18 |
19 |
20 | 23 |
24 |
25 | 26 | Log4J URL 27 |
28 | 29 | -------------------------------------------------------------------------------- /src/main/resources/templates/jwt-auth.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 20 | 21 | 22 | 23 | 24 |
25 |
26 |
27 |
28 |
29 |
Local Session JWT Token:
30 |

 31 |                 
 34 |                 
 35 |             
36 |
37 |
38 |
39 |
Item Search:
40 | 41 | 42 |

 43 |             
44 |
45 |
46 | 47 | 48 | 49 | 89 | 90 | 91 | 174 |
175 | 176 | -------------------------------------------------------------------------------- /src/main/resources/templates/links.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 | 13 |
    14 |
  • 15 | 16 |
  • 17 |
18 | 19 | 20 |
21 | 22 | -------------------------------------------------------------------------------- /src/main/resources/templates/login-form-multi.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 | 13 |
14 |
15 |
16 | 17 |
18 | 19 | 22 | The username of your search account. 23 |
24 |
25 | 26 | 27 |
28 |
29 | 30 | 31 |
32 | 33 |
34 | 35 |
36 | Invalid username and password. 37 |
38 | 39 |
40 | You have been logged out. 41 |
42 |
43 |
44 | 45 |
46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /src/main/resources/templates/login.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 | 13 |
14 |
15 |
16 |
17 | 18 | 21 | The username of your search account. 22 |
23 |
24 | 25 | 26 |
27 | 28 |
29 | 30 |
31 | Invalid username and password. 32 |
33 | 34 |
35 | You have been logged out. 36 |
37 |
38 |
39 | 40 |
41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/main/resources/templates/payload-view.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 | 13 | 14 | 15 | 16 |
17 | 18 | -------------------------------------------------------------------------------- /src/main/resources/templates/payloads.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 | 13 |
    14 |
  • 15 | 16 | 17 |
  • 18 |
19 | 20 | 21 |
22 | 23 | -------------------------------------------------------------------------------- /src/main/resources/templates/search.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
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 | 41 | 42 | 43 | 44 | 45 |
IDNameDescription
46 |
47 |
48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /src/main/resources/templates/token-auth.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 15 | 16 | 17 | 18 |
19 |
20 |
21 |
22 |
23 |
Token Header
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
Item Search:
37 | 38 | 39 |

40 |             
41 |
42 |
43 | 44 | 71 |
72 | 73 | -------------------------------------------------------------------------------- /src/main/resources/templates/user-search.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
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 | 41 | 42 | 43 | 44 | 45 |
IDNameDescription
46 |
47 |
48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /src/main/resources/templates/users.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 20 | 21 | 22 | 23 | 24 |
25 |
26 |
27 |
28 |
29 |
Local Session JWT Token:
30 |

 31 |                 
 34 |                 
 35 |             
36 |
37 |
38 |
39 |
User Search:
40 | 41 | 42 |

 43 |             
44 |
45 |
46 | 47 | 48 | 49 | 89 | 90 | 91 | 174 |
175 | 176 | -------------------------------------------------------------------------------- /stackhawk-actions.yml: -------------------------------------------------------------------------------- 1 | app: 2 | # Update your applicationId 3 | applicationId: ${APP_ID:9690bb4a-aa74-4813-bf93-fde705783f64} 4 | env: ${APP_ENV:GitHub Actions} 5 | host: ${APP_HOST:https://localhost:9000} 6 | excludePaths: 7 | - "/logout" 8 | # - "/login-form-multi" 9 | # - "/login-code" 10 | antiCsrfParam: "_csrf" 11 | # Configure Basic Authentication 12 | authentication: 13 | loggedInIndicator: "\\QSign Out\\E" 14 | loggedOutIndicator: ".*Location:.*/login.*" 15 | usernamePassword: 16 | type: FORM 17 | loginPath: /login 18 | loginPagePath: /login 19 | usernameField: username 20 | passwordField: password 21 | scanUsername: "user" 22 | scanPassword: "password" 23 | cookieAuthorization: 24 | cookieNames: 25 | - "JSESSIONID" 26 | testPath: 27 | path: /search 28 | success: "HTTP.*200.*" 29 | # Utilize OpenAPI Spec, Custom data & Faker 30 | openApiConf: 31 | # path: /openapi 32 | filePath: openapi.yaml 33 | fakerEnabled: true #default false 34 | # includeAllMethods: true 35 | includedMethods: 36 | - POST 37 | - PUT 38 | customVariables: 39 | - field: text 40 | values: 41 | - "$faker:uuid" 42 | - field: searchText 43 | values: 44 | - "$faker:Crypto.sha512" 45 | - "Donec ullamcorper nulla non metus auctor fringilla." 46 | - field: username 47 | values: 48 | - "Andy Dwyer" 49 | - field: password 50 | values: 51 | - "$faker:password" 52 | hawk: 53 | spider: 54 | maxDurationMinutes: 5 55 | # config: 56 | # - "scanner.analyser.redirectEqualsNotFound=false" 57 | # - "scanner.analyser.followRedirect=true" 58 | # Grab Commit SHA and Branch name 59 | tags: 60 | - name: _STACKHAWK_GIT_COMMIT_SHA 61 | value: ${COMMIT_SHA:} 62 | - name: _STACKHAWK_GIT_BRANCH 63 | value: ${BRANCH_NAME:} -------------------------------------------------------------------------------- /stackhawk.d/stackhawk-ajax.yml: -------------------------------------------------------------------------------- 1 | hawk: 2 | spider: 3 | ajax: true 4 | ajaxBrowser: CHROME_HEADLESS 5 | config: 6 | - "ajaxSpider.eventWait=20" 7 | - "ajaxSpider.reloadWait=70" 8 | -------------------------------------------------------------------------------- /stackhawk.d/stackhawk-auth-basic.yml: -------------------------------------------------------------------------------- 1 | app: 2 | env: ${APP_ENV:Basic Web Auth} 3 | authentication: 4 | external: 5 | type: TOKEN 6 | value: ${AUTH_TOKEN} 7 | tokenAuthorization: 8 | type: HEADER 9 | value: Authorization 10 | tokenType: Basic 11 | testPath: 12 | path: /api/basic/items/search/1 13 | success: ".*200.*" 14 | requestMethod: GET 15 | loggedInIndicator: "\\QSign Out\\E" 16 | loggedOutIndicator: ".*Location:.*/login.*" -------------------------------------------------------------------------------- /stackhawk.d/stackhawk-auth-external-jwt.yml: -------------------------------------------------------------------------------- 1 | app: 2 | env: ${APP_ENV:JWT Bearer Token} 3 | openApiConf: 4 | path: /openapi 5 | authentication: 6 | external: 7 | type: TOKEN 8 | value: ${AUTH_TOKEN} 9 | tokenAuthorization: 10 | type: HEADER 11 | value: Authorization 12 | tokenType: Bearer 13 | testPath: 14 | path: /api/jwt/items/search/ 15 | success: ".*200.*" 16 | loggedInIndicator: "\\QSign Out\\E" 17 | loggedOutIndicator: ".*Location:.*/login.*" 18 | 19 | hawk: 20 | spider: 21 | base: false 22 | -------------------------------------------------------------------------------- /stackhawk.d/stackhawk-auth-external-token.yml: -------------------------------------------------------------------------------- 1 | app: 2 | env: ${APP_ENV:External Token} 3 | openApiConf: 4 | path: /openapi 5 | authentication: 6 | external: 7 | type: TOKEN 8 | value: "ITSASECRET" 9 | tokenAuthorization: 10 | type: HEADER 11 | value: "SH_AUTH_TOKEN" 12 | testPath: 13 | path: /api/token/items/search/1 14 | success: ".*200.*" 15 | loggedInIndicator: "\\QSign Out\\E" 16 | loggedOutIndicator: ".*Location:.*/login.*" 17 | hawk: 18 | spider: 19 | base: false 20 | -------------------------------------------------------------------------------- /stackhawk.d/stackhawk-auth-form-cookie.yml: -------------------------------------------------------------------------------- 1 | app: 2 | env: ${APP_ENV:Form Cookie} 3 | excludePaths: 4 | - "/logout" 5 | antiCsrfParam: "_csrf" 6 | authentication: 7 | usernamePassword: 8 | type: FORM 9 | loginPath: /login 10 | loginPagePath: /login 11 | usernameField: username 12 | passwordField: password 13 | scanUsername: "janesmith" 14 | scanPassword: "password" 15 | cookieAuthorization: 16 | cookieNames: 17 | - "JSESSIONID" 18 | testPath: 19 | path: /search 20 | success: ".*200.*" 21 | loggedInIndicator: "\\QSign Out\\E" 22 | loggedOutIndicator: ".*Location:.*/login.*" 23 | -------------------------------------------------------------------------------- /stackhawk.d/stackhawk-auth-json-token.yml: -------------------------------------------------------------------------------- 1 | app: 2 | env: ${APP_ENV:JSON Token} 3 | excludePaths: 4 | - "/logout" 5 | openApiConf: 6 | path: /openapi 7 | authentication: 8 | usernamePassword: 9 | type: JSON 10 | loginPath: /api/jwt/auth/signin 11 | usernameField: username 12 | passwordField: password 13 | scanUsername: "janesmith" 14 | scanPassword: "password" 15 | tokenExtraction: 16 | type: TOKEN_PATH 17 | value: "token" 18 | tokenAuthorization: 19 | type: HEADER 20 | value: Authorization 21 | tokenType: Bearer 22 | testPath: 23 | path: /api/jwt/items/search/i 24 | success: ".*200.*" 25 | loggedInIndicator: "\\QSign Out\\E" 26 | loggedOutIndicator: ".*Location:.*/login.*" 27 | -------------------------------------------------------------------------------- /stackhawk.d/stackhawk-auth-script-form-multi.yml: -------------------------------------------------------------------------------- 1 | app: 2 | applicationId: ${APP_ID:f5ee2290-3383-415c-96c7-ee0a398d90b9} 3 | env: ${APP_ENV:dev} 4 | host: ${APP_HOST:https://localhost:9000} 5 | excludePaths: 6 | - "/logout" 7 | - "/login" 8 | - "/login-form-multi" 9 | - "/login-code" 10 | antiCsrfParam: "_csrf" 11 | authentication: 12 | loggedInIndicator: "\\QSign Out\\E|^HTTP.*20[0-9]|^HTTP.*50[0-9]|^HTTP.*40[0-9]" 13 | loggedOutIndicator: ".*Location:.*\\/login.*" 14 | script: 15 | name: form-auth-multi.kts 16 | credentials: 17 | username: user 18 | password: password 19 | parameters: 20 | loginPagePath: /login-form-multi 21 | loginPage: /login-form-multi 22 | logging: "true" 23 | formType: "FORM" 24 | remember: "on" 25 | csrfExtra: "loginCode" 26 | cookieAuthorization: 27 | cookieNames: 28 | - "JSESSIONID" 29 | - "XLOGINID" 30 | testPath: 31 | path: /search 32 | success: "HTTP.*200.*" 33 | hawk: 34 | spider: 35 | maxDurationMinutes: 5 36 | hawkAddOn: 37 | scripts: 38 | - name: form-auth-multi.kts 39 | language: KOTLIN 40 | type: authentication 41 | path: hawkscripts 42 | # - name: custom-sender.kts 43 | # language: KOTLIN 44 | # type: httpsender 45 | # path: hawkscripts 46 | -------------------------------------------------------------------------------- /stackhawk.d/stackhawk-custom-spider-curl.yml: -------------------------------------------------------------------------------- 1 | app: 2 | applicationId: ${APP_ID:f5ee2290-3383-415c-96c7-ee0a398d90b9} 3 | env: ${APP_ENV:dev} 4 | host: ${APP_HOST:https://localhost:9000} 5 | excludePaths: 6 | - "/logout" 7 | # - "/login-form-multi" 8 | # - "/login-code" 9 | antiCsrfParam: "_csrf" 10 | authentication: 11 | loggedInIndicator: "\\QSign Out\\E" 12 | loggedOutIndicator: ".*Location:.*/login.*" 13 | usernamePassword: 14 | type: FORM 15 | loginPath: /login 16 | loginPagePath: /login 17 | usernameField: username 18 | passwordField: password 19 | scanUsername: "janesmith" 20 | scanPassword: "password" 21 | cookieAuthorization: 22 | cookieNames: 23 | - "JSESSIONID" 24 | testPath: 25 | path: /search 26 | success: "HTTP.*200.*" 27 | hawk: 28 | spider: 29 | base: false 30 | custom: 31 | command: curl -x $HTTP_PROXY -k ${APP_HOST:https://localhost:9000}/login?nuance=true 32 | 33 | -------------------------------------------------------------------------------- /stackhawk.d/stackhawk-custom-spider-newman.yml: -------------------------------------------------------------------------------- 1 | app: 2 | applicationId: ${APP_ID:f5ee2290-3383-415c-96c7-ee0a398d90b9} 3 | env: ${APP_ENV:dev} 4 | host: ${APP_HOST:https://localhost:9000} 5 | excludePaths: 6 | - "/logout" 7 | # - "/login-form-multi" 8 | # - "/login-code" 9 | antiCsrfParam: "_csrf" 10 | authentication: 11 | loggedInIndicator: "\\QSign Out\\E" 12 | loggedOutIndicator: ".*Location:.*/login.*" 13 | usernamePassword: 14 | type: FORM 15 | loginPath: /login 16 | loginPagePath: /login 17 | usernameField: username 18 | passwordField: password 19 | scanUsername: "janesmith" 20 | scanPassword: "password" 21 | cookieAuthorization: 22 | cookieNames: 23 | - "JSESSIONID" 24 | testPath: 25 | path: /search 26 | success: "HTTP.*200.*" 27 | hawk: 28 | spider: 29 | maxDurationMinutes: 5 30 | base: false 31 | custom: 32 | command: newman run javaspringvulny_postman_collection.json --verbose --global-var baseUrl=${APP_HOST:https://localhost:9000} --insecure 33 | logOutputToForeground: true 34 | 35 | -------------------------------------------------------------------------------- /stackhawk.d/stackhawk-fuzzer.yaml: -------------------------------------------------------------------------------- 1 | app: 2 | inputVectors: 3 | injectableParam: 4 | urlPath: true 5 | urlQuery: true 6 | urlQueryParam: true 7 | # httpHeaders: true 8 | #hawk: 9 | # scan: 10 | # policyName: CUSTOM_SCRIPTS 11 | 12 | hawkAddOn: 13 | scripts: 14 | - name: fuzzer.kts 15 | type: active 16 | path: hawkscripts 17 | id: 1000000 # Replace with your own registered plugin ID 18 | language: KOTLIN 19 | vars: 20 | - name: iterations 21 | val: 10 22 | - name: stringStartLength 23 | val: 50 24 | - name: stringEndLength 25 | val: 10_000 -------------------------------------------------------------------------------- /stackhawk.d/stackhawk-github-pr.yml: -------------------------------------------------------------------------------- 1 | tags: 2 | - name: _STACKHAWK_GIT_COMMIT_SHA 3 | value: ${COMMIT_SHA} 4 | - name: _STACKHAWK_GIT_BRANCH 5 | value: ${BRANCH_NAME} -------------------------------------------------------------------------------- /stackhawk.d/stackhawk-har.yml: -------------------------------------------------------------------------------- 1 | hawk: 2 | spider: 3 | base: false 4 | har: 5 | dir: 6 | path: ${HOME}/test/resources/har 7 | file: 8 | paths: 9 | - ${HOME}/test/resources/har/localhost-jsv-1.har 10 | - ${HOME}/test/resources/har/localhost-jsv-4.har 11 | app: 12 | applicationId: ${APP_ID:dacc7d3e-babc-47d2-b040-ab117ab04526} 13 | env: ${APP_ENV:Development} 14 | host: ${APP_HOST:https://localhost:9000} 15 | excludePaths: 16 | - "/logout" 17 | antiCsrfParam: "_csrf" 18 | authentication: 19 | loggedInIndicator: "\\QSign Out\\E" 20 | loggedOutIndicator: ".*Location:.*/login.*" 21 | usernamePassword: 22 | type: FORM 23 | loginPath: /login 24 | loginPagePath: /login 25 | usernameField: username 26 | passwordField: password 27 | scanUsername: "janesmith" 28 | scanPassword: "password" 29 | cookieAuthorization: 30 | cookieNames: 31 | - "JSESSIONID" 32 | testPath: 33 | path: /search 34 | success: "HTTP.*200.*" 35 | inputVectors: 36 | injectableParam: 37 | postData: true 38 | urlQuery: true 39 | urlQueryParam: true 40 | enabledRpcParam: 41 | multipartFormData: true 42 | json: true 43 | xmlTag: true 44 | -------------------------------------------------------------------------------- /stackhawk.d/stackhawk-jsv-form-cookie.yml: -------------------------------------------------------------------------------- 1 | hawk: 2 | spider: 3 | maxDurationMinutes: 2 4 | app: 5 | env: ${APP_ENV:dev} 6 | openApiConf: 7 | path: /openapi.json 8 | host: ${APP_HOST:https://localhost:9000} 9 | excludePaths: 10 | - "/logout" 11 | antiCsrfParam: "_csrf" 12 | authentication: 13 | loggedInIndicator: "\\QSign Out\\E" 14 | loggedOutIndicator: ".*Location:.*/login.*" 15 | usernamePassword: 16 | type: FORM 17 | loginPath: /login 18 | loginPagePath: /login 19 | usernameField: username 20 | passwordField: password 21 | scanUsername: "janesmith" 22 | scanPassword: "password" 23 | cookieAuthorization: 24 | cookieNames: 25 | - "JSESSIONID" 26 | testPath: 27 | path: /search 28 | success: ".*200.*" 29 | -------------------------------------------------------------------------------- /stackhawk.d/stackhawk-jsv-json-token.yml: -------------------------------------------------------------------------------- 1 | hawk: 2 | spider: 3 | maxDurationMinutes: 2 4 | app: 5 | env: ${APP_ENV:dev} 6 | openApiConf: 7 | path: /openapi 8 | host: ${APP_HOST:https://localhost:9000} 9 | authentication: 10 | loggedInIndicator: "\\QSign Out\\E" 11 | loggedOutIndicator: ".*Location:.*/login.*" 12 | usernamePassword: 13 | type: JSON 14 | loginPath: /api/jwt/auth/signin 15 | usernameField: username 16 | passwordField: password 17 | scanUsername: "janesmith" 18 | scanPassword: "password" 19 | tokenAuthorization: 20 | type: HEADER 21 | value: Authorization 22 | tokenType: Bearer 23 | tokenExtraction: 24 | type: TOKEN_PATH 25 | value: "token" 26 | testPath: 27 | path: /api/jwt/items/search/i 28 | success: ".*200.*" 29 | -------------------------------------------------------------------------------- /stackhawk.d/stackhawk-multi-cookie-auth.yml: -------------------------------------------------------------------------------- 1 | app: 2 | applicationId: ${APP_ID:test-app} 3 | env: ${APP_ENV:Multi Cookie Auth} 4 | openApiConf: 5 | path: /openapi 6 | host: ${HOST:https://localhost:9000} 7 | excludePaths: 8 | - "/logout" 9 | - "/login-form-multi" 10 | - "/login-code" 11 | authentication: 12 | external: 13 | values: 14 | - type: COOKIE 15 | value: 16 | name: "XLOGINID" 17 | val: ${XLOGINID} 18 | - type: COOKIE 19 | value: 20 | name: "JSESSIONID" 21 | val: ${JSESSIONID} 22 | testPath: 23 | path: /login-multi-check 24 | success: ".*200.*" 25 | loggedInIndicator: "\\QSign Out\\E" 26 | loggedOutIndicator: ".*Location:.*/login.*" 27 | -------------------------------------------------------------------------------- /stackhawk.d/stackhawk-okta.yml: -------------------------------------------------------------------------------- 1 | app: 2 | applicationId: ${APP_ID:f5ee2290-3383-415c-96c7-ee0a398d90b9} 3 | env: ${APP_ENV:dev} 4 | host: ${APP_HOST:https://localhost:9000} 5 | 6 | authentication: 7 | script: 8 | name: okta-client-credentials-basic.kts 9 | parameters: 10 | okta_domain: '${OKTA_DOMAIN:changeme.okta.com}' 11 | scope: api_key 12 | credentials: 13 | client_id: '${OKTA_CLIENT_ID:changeme}' 14 | client_secret: '${OKTA_CLIENT_SECRET:changeme}' 15 | sessionScript: 16 | name: okta-json-to-token.kts 17 | parameters: 18 | jwt_token_field: access_token 19 | testPath: 20 | path: /api/okta/me/token 21 | success: 'HTTP(.*)200.*' 22 | loggedInIndicator: '.*' 23 | loggedOutIndicator: '' 24 | hawkAddOn: 25 | scripts: 26 | - name: okta-client-credentials-basic.kts 27 | type: authentication 28 | path: hawkscripts 29 | language: KOTLIN 30 | - name: okta-json-to-token.kts 31 | type: session 32 | path: hawkscripts 33 | language: KOTLIN 34 | - name: log-http-payloads.kts 35 | type: httpsender 36 | path: hawkscripts 37 | language: KOTLIN -------------------------------------------------------------------------------- /stackhawk.d/stackhawk-openapi.yml: -------------------------------------------------------------------------------- 1 | app: 2 | applicationId: ${APP_ID:f5ee2290-3383-415c-96c7-ee0a398d90b9} 3 | env: ${APP_ENV:dev-api} 4 | host: ${APP_HOST:https://localhost:9000} 5 | excludePaths: 6 | - "/logout" 7 | antiCsrfParam: "_csrf" 8 | authentication: 9 | loggedInIndicator: "HTTP.*200.*" 10 | loggedOutIndicator: ".*Location:.*/login.*" 11 | usernamePassword: 12 | type: JSON 13 | loginPath: /api/jwt/auth/signin 14 | usernameField: username 15 | passwordField: password 16 | scanUsername: "janesmith" 17 | scanPassword: "password" 18 | tokenAuthorization: 19 | type: HEADER 20 | value: Authorization 21 | tokenType: Bearer 22 | tokenExtraction: 23 | type: TOKEN_PATH 24 | value: "token" 25 | testPath: 26 | path: /api/jwt/items/search/i 27 | success: "HTTP.*200.*" 28 | 29 | openApiConf: 30 | forbiddenVariables: 31 | - field: zone_id 32 | values: 33 | - "" 34 | # path: /openapi 35 | filePath: openapi.yaml 36 | includeAllMethods: true 37 | includedMethods: 38 | - POST 39 | - PUT 40 | customVariables: 41 | - field: text 42 | values: 43 | - "customTextValue1" 44 | - "customTextValue2" 45 | - "customTextValue3" 46 | - field: Dana 47 | values: 48 | - "no judgment!" 49 | - "quiet you!" 50 | - "shoosh!" 51 | - "something something... your face!" 52 | - field: searchText 53 | values: 54 | - "customSearchText1" 55 | - "customSearchText2" 56 | - "customSearchText3" 57 | - field: username 58 | values: 59 | - "username1" 60 | - "username2" 61 | - "username3" 62 | -------------------------------------------------------------------------------- /stackhawk.d/stackhawk.yml: -------------------------------------------------------------------------------- 1 | app: 2 | applicationId: ${APP_ID:f5ee2290-3383-415c-96c7-ee0a398d90b9} 3 | env: ${APP_ENV:dev} 4 | host: ${APP_HOST:https://localhost:9000} 5 | -------------------------------------------------------------------------------- /stackhawk.yml: -------------------------------------------------------------------------------- 1 | app: 2 | applicationId: ${APP_ID:747797df-f803-4cf4-9486-7020f58b8deb} 3 | env: ${APP_ENV:Development} 4 | host: ${APP_HOST:https://localhost:9000} 5 | waitForAppTarget: 6 | path: / 7 | waitTimeoutMillis: 200000 8 | pollDelay: 500 9 | excludePaths: 10 | - "/logout" 11 | antiCsrfParam: "_csrf" 12 | authentication: 13 | loggedInIndicator: "\\QSign Out\\E" 14 | loggedOutIndicator: ".*Location:.*/login.*" 15 | usernamePassword: 16 | type: FORM 17 | loginPath: /login 18 | loginPagePath: /login 19 | usernameField: username 20 | passwordField: password 21 | scanUsername: "janesmith" 22 | scanPassword: "password" 23 | cookieAuthorization: 24 | cookieNames: 25 | - "JSESSIONID" 26 | testPath: 27 | path: /search 28 | success: "HTTP.*200.*" 29 | -------------------------------------------------------------------------------- /tools/update-openapi-files.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Exit on error 4 | set -e 5 | 6 | # Define the URL of the running application 7 | APP_URL="https://localhost:9000/openapi" 8 | HEALTH_URL="https://localhost:9000/actuator/health" 9 | 10 | # Define the output files 11 | JSON_OUTPUT="openapi.json" 12 | YAML_OUTPUT="openapi.yaml" 13 | 14 | # Save the current directory 15 | CURRENT_DIR=$(pwd) 16 | 17 | # Change to the parent directory 18 | cd .. 19 | 20 | # Start the application in the background 21 | ./gradlew bootRun & 22 | APP_PID=$! 23 | 24 | # Wait for the application to start by checking the health endpoint 25 | until $(curl -k --output /dev/null --silent --head --fail ${HEALTH_URL}); do 26 | printf '.' 27 | sleep 5 28 | done 29 | 30 | # Fetch the OpenAPI JSON definition and save it to openapi.json 31 | curl -k -s "${APP_URL}" -o "${JSON_OUTPUT}" 32 | 33 | # Kill the application process 34 | kill $APP_PID 35 | 36 | # Reformat the JSON file using jq 37 | jq . "${JSON_OUTPUT}" > "${JSON_OUTPUT}.tmp" && mv "${JSON_OUTPUT}.tmp" "${JSON_OUTPUT}" 38 | 39 | # Convert the JSON definition to YAML and save it to openapi.yaml 40 | # Requires yq (https://github.com/mikefarah/yq) to be installed 41 | if command -v yq &> /dev/null 42 | then 43 | yq eval -P "${JSON_OUTPUT}" > "${YAML_OUTPUT}" 44 | else 45 | echo "yq is not installed. Please install yq to convert JSON to YAML." 46 | kill $APP_PID 47 | fi 48 | 49 | # Change back to the original directory 50 | cd "${CURRENT_DIR}" 51 | 52 | echo "OpenAPI definitions updated successfully." 53 | --------------------------------------------------------------------------------