32 examples

Path traversal

Unauthorized directory access via manipulated paths.

[ FAQ1 ]

What is path traversal?

Path traversal, also known as directory traversal, is a security vulnerability where an attacker crafts malicious input to exploit insecure file handling, allowing them to navigate outside the intended directory structure. By using special characters like ../ in file paths, attackers attempt to access sensitive system files, configuration data, or other restricted resources. Path traversal vulnerabilities occur due to inadequate validation and sanitization of user-supplied file paths, leading to potential data breaches, information disclosure, or even full system compromise.
[ FAQ2 ]

How to prevent path traversal vulnerabilities

To prevent path traversal vulnerabilities, always validate and sanitize user inputs thoroughly before using them to access the file system. Use strict allowlisting to ensure file operations remain confined within secure, intended directories. Normalize file paths to resolve and eliminate special characters (../, ./) and ensure final paths remain within defined directory boundaries. Employ secure APIs or built-in language methods specifically designed for safe file handling, reducing the risk of traversal attacks. Regularly review and test your application's file-handling logic and adopt secure coding practices, consistently applying file path validation across all parts of your system.
diff block
return;
}
+ // Authentication is handled on a route-level here.
+ if (req.url.includes("/api/v1/workflow-integrations/microsoft-teams/message-endpoint")) {
+ return;
+ }
Greptile
greptile
logic: Using string.includes() for URL path matching is vulnerable to path traversal attacks. Consider using exact path matching or a URL parser.
suggested fix
// Authentication is handled on a route-level here.
+ if (req.url === "/api/v1/workflow-integrations/microsoft-teams/message-endpoint") {
return;
}
diff block
},
"types": ["node", "vite/client"]
},
- "include": ["src/**/*", "tailwind.config.ts", "postcss.config.js"]
+ "include": [
+ "src/**/*",
+ "tailwind.config.ts",
+ "postcss.config.js",
+ "../../../playground-ui/src/store/playground-store.ts"
Greptile
greptile
style: Using relative path traversal (../../../) is fragile. Consider using a module alias or moving shared code to a common location.
diff block
+package org.owasp.webgoat.lessons.pathtraversal;
+
+import static org.springframework.http.MediaType.ALL_VALUE;
+import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
+
+import org.owasp.webgoat.container.CurrentUsername;
+import org.owasp.webgoat.container.assignments.AssignmentHints;
+import org.owasp.webgoat.container.assignments.AttackResult;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+
+@RestController
+@AssignmentHints({
+ "path-traversal-profile-fix.hint1",
+ "path-traversal-profile-fix.hint2",
+ "path-traversal-profile-fix.hint3"
+})
+public class ProfileUploadFix extends ProfileUploadBase {
+
+ public ProfileUploadFix(@Value("${webgoat.server.directory}") String webGoatHomeDirectory) {
+ super(webGoatHomeDirectory);
+ }
+
+ @PostMapping(
+ value = "/PathTraversal/profile-upload-fix",
+ consumes = ALL_VALUE,
+ produces = APPLICATION_JSON_VALUE)
+ @ResponseBody
+ public AttackResult uploadFileHandler(
+ @RequestParam("uploadedFileFix") MultipartFile file,
+ @RequestParam(value = "fullNameFix", required = false) String fullName,
+ @CurrentUsername String username) {
+ return super.execute(file, fullName != null ? fullName.replace("../", "") : "", username);
Greptile
greptile
logic: Simple string replacement of '../' is insufficient for path traversal protection. Attackers can bypass with encoded variants like '..%2F' or '....//'. Consider using a canonical path validation or path normalization library.
diff block
+#
+# This is a picoweb example showing the usage of
+# extra headers in responses.
+#
+import picoweb
+import ure as re
+
+app = picoweb.WebApp(__name__)
+
+# Shows sending extra headers specified as a dictionary.
+@app.route("/")
+def index(req, resp):
+
+ headers = {"X-MyHeader1": "foo", "X-MyHeader2": "bar"}
+
+ # Passing headers as a positional param is more efficient,
+ # but we pass by keyword here ;-)
+ yield from picoweb.start_response(resp, headers=headers)
+ yield from resp.awrite(b"""\
+<!DOCTYPE html>
+<html>
+<head>
+<link href="style.css" rel="stylesheet">
+</head>
+<body>
+<p>The style.css should be cached and might be encoded.</p>
+<p class="green">Check out your webdev tool!</p>
+</body>
+</html>""")
+
+
+# Send gzipped content if supported by client.
+# Shows specifying headers as a flat binary string -
+# more efficient if such headers are static.
+@app.route(re.compile('^\/(.+\.css)$'))
+def styles(req, resp):
+ file_path = req.url_match.group(1)
Greptile
greptile
logic: potential path traversal vulnerability - file_path should be sanitized before use in file operations
suggested fix
file_path = req.url_match.group(1)
+ # Prevent path traversal by removing path separators and parent directory references
+ file_path = file_path.replace('..', '').replace('/', '').replace('\\', '')
diff block
-# WebGoat release notes
+# WebGoat release notes
-## Unreleased
+## Version 2023.8
+
+### 🚀 New functionality
+
+- Consistent environment values and url references (#1677)
+- Show directly requested file in requests overview
+- Show creating time in file upload overview
+
+### 🐞 Bug fixes
+
+- Fix startup message (#1687)
+- Fix/state of software supply chain links (#1683)
+- Fix WebWolf UI (#1686)
+
+### 🔄 Technical tasks
+
+- bump actions/setup-java from 3 to 4 (#1690)
+- bump commons-io:commons-io from 2.14.0 to 2.15.1 (#1689)
+- bump com.diffplug.spotless:spotless-maven-plugin (#1688)
+
+## Version 2023.5
+
+### New functionality
+
+- Implement JWT jku example (#1552)
+- Java 21 initial support (#1622)
+- improve MFAC lesson hint texts for a better user experience (#1424)
+- upgrade to Spring Boot version 3 (#1477)
+
+### Bug fixes
+
+- typo in WebGoad.txt (#1667)
+- search box moved and jwt encode/decode with little delay (#1664)
+- skip validation for JWT (#1663)
+- fixed issue in JWT test tool and added robot test (#1658)
+- Password reset link test condition more strict and move all WebWolf links to /WebWolf (#1645)
+- fix servers id (#1619)
+- potential NPE in the stored XSS assignment
+- crypto basics broken links
+- fixes the default change in trailing slash matching and address the affected assignments
+- hint that was breaking the template, causing hints from different assignments to mix (#1424)
+- HijackSession lesson template deprecated Tymeleaf attribute
+- Fix NPE in IDOR lesson
+- Add new assignment IT tests
+- XSS mitigation
+- Stored Cross-Site Scripting Lesson
+- Add Assignment7 Tests
+- Fix IDOR lesson
+- remove steps from release script (#1509)
+- robotframework fails due to updated dependencies (#1508)
+- fix Java image inside Docker file The image now downloads the correct Java version based on the architecture.
+- Fix typo of HijackSession_content0.adoc
+- Restrict SSRF Regexes
+- update challenge code - Flags are now wired through a Spring config - Introduced Flag class - Removed Flags from the FlagController
+
+## Version 2023.4
+
+### New functionality
+
+- [#1422 Add Docker Linux Desktop variant with all tools installed](https://github.com/WebGoat/WebGoat/issues/1422). Thanks to the [OWASP WrongSecrets project](https://owasp.org/www-project-wrongsecrets/) we now have a Docker Linux desktop image with all the tools installed. No need to install any tools locally only run the new Docker image. See README.md for details on how to start it.
+- [#1411 JWT: looks that buy as Tom also works with alg:none](https://github.com/WebGoat/WebGoat/issues/1411).
+
+### Bug fixes
+
+- [#1410 WebWolf: JWT decode is broken](https://github.com/WebGoat/WebGoat/issues/1410).
+- [#1396 password_reset return 500 Error](https://github.com/WebGoat/WebGoat/issues/1396).
+- [#1379 Move XXE to A05:2021-Security Misconfiguration](https://github.com/WebGoat/WebGoat/issues/1379).
+
+## Version 2023.3
+
+With great pleasure, we present you with a new release of WebGoat **2023.3**. Finally, it has been a while. This year starts with a new release of WebGoat. This year we will undoubtedly release more often. From this release on, we began to use a new versioning scheme (https://calver.org/#scheme).
+
+A big thanks to René Zubcevic and Àngel Ollé Blázquez for keeping the project alive this last year, and hopefully, we can make
+many more releases this year.
### New functionality
-- New year's resolution: major refactoring of WebGoat to simplify the setup and improve building times.
+- New year's resolution(2022): major refactoring of WebGoat to simplify the setup and improve building times.
- Move away from multi-project setup:
- - This has a huge performance benefit when building the application. Build time locally is now `Total time: 42.469 s` (depends on your local machine of course)
- - No longer add Maven dependencies in several places
- - H2 no longer needs to run as separate process, which solves the issue of WebWolf sharing and needing to configure the correct database connection.
+ * This has a huge performance benefit when building the application. Build time locally is now `Total time: 42.469 s` (depends on your local machine of course)
+ * No longer add Maven dependencies in several places
+ * H2 no longer needs to run as separate process, which solves the issue of WebWolf sharing and needing to configure the correct database connection.
- More explicit paths in html files to reference `adoc` files, less magic.
- Integrate WebWolf in WebGoat, the setup was way too complicated and needed configuration which could lead to mistakes and a not working application. This also simplifies the Docker configuration as there is only 1 Docker image.
- Add WebWolf button in WebGoat
-- Move all lessons into `src/main/resources`
-- WebGoat selects a port dynamically when starting. It will still start of port 8080 it will try another port to ease the user experience.
+- Move all lessons into `src/main/resources`
+- WebGoat selects a port dynamically when starting. It will still start of port 8080 it will try another port to ease the user experience.
- WebGoat logs URL after startup: `Please browse to http://127.0.0.1:8080/WebGoat to get started...`
- Simplify `Dockerfile` as we no longer need a script to start everything
- Maven build now start WebGoat jar with Maven plugin to make sure we run against the latest build.
- Added `Initializable` interface for a lesson, an assignment can implement this interface to set it up for a specific user and to reset the assignment back to its original state when a reset lesson occurs. See `BlindSendFileAssignment` for an example.
- Integration tests now use the same user. This saves a lot of time as before every test used a different user which triggered the Flyway migration to set up the database schema for the user. This migration took a lot of time.
+- Updated introduction lesson to WebWolf.
+- Added language switch for support for multiple languages.
+- Removed logic to start WebGoat on a random port when port `8080` is taken. We would loop until we found a free port. We simplified this to just start on the specified port.
+- Add Google formatter for all our code, a PR now checks whether the code adheres to the standard.
+- Renaming of all packages and folders.
+- [#1039 New OWASP Top 10](https://github.com/WebGoat/WebGoat/issues/1093)
+- [#1065 New lesson about logging](https://github.com/WebGoat/WebGoat/issues/1065)
+
+### Bug fixes
+
+- [#1193 Vulnerable component lesson - java.desktop does not "opens java.beans" to unnamed module](https://github.com/WebGoat/WebGoat/issues/1193)
+- [#1176 Minor: XXE lesson 12 patch not reset by 'lesson reset' while it IS reset by leaving/returning to lesson](https://github.com/WebGoat/WebGoat/issues/1176)
+- [#1134 "Exploiting XStream" assignment does not work](https://github.com/WebGoat/WebGoat/issues/1134)
+- [#1130 Typo: Using Indrect References](https://github.com/WebGoat/WebGoat/issues/1130)
+- [#1101 SQL lesson not correct](https://github.com/WebGoat/WebGoat/issues/1101)
+- [#1079 startup.sh issues of WebWolf - cannot connect to the WebGoat DB](https://github.com/WebGoat/WebGoat/issues/1079)
+- [#1379 Move XXE to A05:2021-_Security_ Misconfiguration](https://github.com/WebGoat/WebGoat/issues/1379)
+- [#1298 SocketUtils is deprecated and will be removed in Spring Security 6](https://github.com/WebGoat/WebGoat/issues/1298)
+- [#1248 Rewrite the WebWolf Introduction Lesson with the new changes](https://github.com/WebGoat/WebGoat/issues/1248)
+- [#1200 Type cast error in sample code at JWT token section](https://github.com/WebGoat/WebGoat/issues/1200)
+- [#1173 --server.port=9000 is not respected on Windows (both cmd as Powershell)](https://github.com/WebGoat/WebGoat/issues/1173)
+- [#1103 (A1) path traversel lesson 7 seems broken](https://github.com/WebGoat/WebGoat/issues/1103)
Greptile
greptile
syntax: Typo: 'traversel' should be 'traversal'
suggested fix
+- [#1103 (A1) path traversal lesson 7 seems broken](https://github.com/WebGoat/WebGoat/issues/1103)
diff block
+package org.owasp.webgoat.lessons.pathtraversal;
+
+import static org.owasp.webgoat.container.assignments.AttackResultBuilder.failed;
+import static org.owasp.webgoat.container.assignments.AttackResultBuilder.success;
+
+import jakarta.annotation.PostConstruct;
+import jakarta.servlet.http.HttpServletRequest;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.file.Files;
+import java.util.Base64;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.RandomUtils;
+import org.owasp.webgoat.container.CurrentUsername;
+import org.owasp.webgoat.container.assignments.AssignmentEndpoint;
+import org.owasp.webgoat.container.assignments.AssignmentHints;
+import org.owasp.webgoat.container.assignments.AttackResult;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.core.token.Sha512DigestUtils;
+import org.springframework.util.FileCopyUtils;
+import org.springframework.util.StringUtils;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@AssignmentHints({
+ "path-traversal-profile-retrieve.hint1",
+ "path-traversal-profile-retrieve.hint2",
+ "path-traversal-profile-retrieve.hint3",
+ "path-traversal-profile-retrieve.hint4",
+ "path-traversal-profile-retrieve.hint5",
+ "path-traversal-profile-retrieve.hint6"
+})
+@Slf4j
+public class ProfileUploadRetrieval implements AssignmentEndpoint {
+ private final File catPicturesDirectory;
+
+ public ProfileUploadRetrieval(@Value("${webgoat.server.directory}") String webGoatHomeDirectory) {
+ this.catPicturesDirectory = new File(webGoatHomeDirectory, "/PathTraversal/" + "/cats");
+ this.catPicturesDirectory.mkdirs();
+ }
+
+ @PostConstruct
+ public void initAssignment() {
+ for (int i = 1; i <= 10; i++) {
+ try (InputStream is =
+ new ClassPathResource("lessons/pathtraversal/images/cats/" + i + ".jpg")
+ .getInputStream()) {
+ FileCopyUtils.copy(is, new FileOutputStream(new File(catPicturesDirectory, i + ".jpg")));
+ } catch (Exception e) {
+ log.error("Unable to copy pictures" + e.getMessage());
+ }
+ }
+ var secretDirectory = this.catPicturesDirectory.getParentFile().getParentFile();
+ try {
+ Files.writeString(
+ secretDirectory.toPath().resolve("path-traversal-secret.jpg"),
+ "You found it submit the SHA-512 hash of your username as answer");
+ } catch (IOException e) {
Greptile
greptile
style: Secret file stored in predictable location with hardcoded name, making it easier to target in path traversal attacks.
diff block
allow_origins=["http://localhost:3000", "https://teacherflow.ai", "https://www.teacherflow.ai"],
allow_credentials=True,
allow_methods=["POST", "GET", "DELETE", "OPTIONS"],
- allow_headers=["Content-Type"],
+ allow_headers=["Content-Type", "Range"],
)
# Constants
VIDEOS_DIR = Path("videos")
VIDEOS_DIR.mkdir(exist_ok=True)
-app.mount("/videos", StaticFiles(directory="videos"), name="videos")
+# Remove StaticFiles mount and add streaming endpoint
+@app.get("/videos/{video_filename}")
+async def stream_video(video_filename: str, request: Request):
+ """Stream video with support for range requests"""
+ video_path = VIDEOS_DIR / video_filename
Greptile
greptile
logic: Path traversal vulnerability: video_filename needs to be sanitized before joining with VIDEOS_DIR
suggested fix
+ if "/" in video_filename or "\\" in video_filename:
+ raise HTTPException(status_code=400, detail="Invalid filename")
video_path = VIDEOS_DIR / video_filename
diff block
public class MvcConfiguration implements WebMvcConfigurer {
@Value("${webwolf.fileserver.location}")
- private String fileLocatation;
+ private String fileLocation;
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
- registry.addResourceHandler("/files/**").addResourceLocations("file:///" + fileLocatation + "/");
+ registry.addResourceHandler("/files/**").addResourceLocations("file:///" + fileLocation + "/");
Greptile
greptile
logic: File path concatenation could potentially lead to path traversal if fileLocation is not properly sanitized
diff block
+/**
+ * MediaStore.ts
+ * ---------------
+ *
+ * Generic class to store media in the appData directoory in electron.
+ * This application requires an ImageStore and a VideoStore which calls
+ * the appropriate methods.
+ */
+import * as path from 'path'
+import * as fs from 'fs'
+import * as crypto from 'crypto'
+
+interface FileType {
+ ext: string
+ mime: string
+}
+
+export class MediaStore {
+ protected storageDir: string
+ protected metadataFile: string
+ protected mediaType: string
+
+ constructor(appDataPath: string, mediaType: string) {
+ this.storageDir = path.join(appDataPath, mediaType)
+ this.metadataFile = path.join(appDataPath, `${mediaType}Metadata.json`)
+ this.mediaType = mediaType
+ this.initStorage()
+ }
+
+ protected initStorage(): void {
+ if (!fs.existsSync(this.storageDir)) {
+ fs.mkdirSync(this.storageDir, { recursive: true })
+ }
+ if (!fs.existsSync(this.metadataFile)) {
+ fs.writeFileSync(this.metadataFile, JSON.stringify({}))
+ }
+ }
+
+ protected getMetadata(): Record<string, MediaMetadata> {
+ const data = fs.readFileSync(this.metadataFile, 'utf8')
+ return JSON.parse(data)
+ }
+
+ protected saveMetadata(metadata: Record<string, MediaMetadata>): void {
+ fs.writeFileSync(this.metadataFile, JSON.stringify(metadata))
+ }
+
+ protected generateFileName(originalName: string): string {
+ const timestamp = Date.now()
+ const hash = crypto.createHash('md5').update(`${originalName}${timestamp}`).digest('hex')
+ return hash
+ }
+
+ protected async checkFileType(buffer: any): Promise<FileType | undefined> {
+ const { fileTypeFromBuffer } = await import('file-type')
+ const type = await fileTypeFromBuffer(buffer)
+ return type
+ }
+
+ protected validateFileType(mimeType: string): boolean {
+ return this.mediaType === 'images'
+ ? ['image/png', 'image/jpeg', 'image/gif', 'image/svg+xml'].includes(mimeType)
+ : ['video/mp4', 'video/webm', 'video/quicktime'].includes(mimeType)
+ }
+
+ public async storeMedia(mediaData: string, originalName: string, blockId: string): Promise<string> {
+ try {
+ const base64Data = mediaData.replace(/^data:[^;]+;base64,/, '')
+ const buffer: any = Buffer.from(base64Data, 'base64')
+
+ const type = await this.checkFileType(buffer)
+ if (!type || !this.validateFileType(type.mime)) {
+ throw new Error(`Invalid ${this.mediaType.slice(0, -1)} file type`)
+ }
+
+ const fileName = `${this.generateFileName(originalName)}.${type.ext}`
+ const filePath = path.join(this.storageDir, fileName)
+
+ await fs.promises.writeFile(filePath, buffer)
+
+ const metadata = this.getMetadata()
+ metadata[blockId] = {
+ id: blockId,
+ fileName,
+ originalName,
+ mimeType: type.mime,
+ createdAt: new Date().toISOString(),
+ fileSize: buffer.length,
+ }
+ this.saveMetadata(metadata)
+ return `local://${fileName}`
+ } catch (error) {
+ console.error(`Error storing ${this.mediaType.slice(0, -1)}:`, error)
+ throw error
+ }
+ }
+
+ public async getMedia(fileName: string): Promise<string | null> {
+ try {
+ const updatedFileName = fileName.replace('local://', '')
+ const filePath = path.join(this.storageDir, updatedFileName)
+ if (!fs.existsSync(filePath)) return null
Greptile
greptile
logic: Path traversal vulnerability: fileName should be sanitized before joining with storageDir
suggested fix
+ const updatedFileName = fileName.replace('local://', '').replace(/\.\./g, '')
+ const normalizedPath = path.normalize(updatedFileName)
+ if (normalizedPath.startsWith('..')) throw new Error('Invalid file path')
+ const filePath = path.join(this.storageDir, normalizedPath)
if (!fs.existsSync(filePath)) return null
diff block
+import { request } from "@app/lib/config/request";
+import { removeTrailingSlash } from "@app/lib/fn";
+import { getHCVaultAccessToken } from "@app/services/app-connection/hc-vault";
+import {
+ THCVaultListVariables,
+ THCVaultListVariablesResponse,
+ THCVaultSyncWithCredentials,
+ TPostHCVaultVariable
+} from "@app/services/secret-sync/hc-vault/hc-vault-sync-types";
+import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors";
+import { TSecretMap } from "@app/services/secret-sync/secret-sync-types";
+
+const listHCVaultVariables = async ({ instanceUrl, mount, accessToken, path }: THCVaultListVariables) => {
+ const { data } = await request.get<THCVaultListVariablesResponse>(
+ `${instanceUrl}/v1/${removeTrailingSlash(mount)}/data/${path}`,
+ {
Greptile
greptile
logic: URL construction from user input could be vulnerable to path traversal. Consider validating/sanitizing path and mount parameters.
diff block
+import { Controller, useFormContext, useWatch } from "react-hook-form";
+import { SingleValue } from "react-select";
+import { faCircleInfo } from "@fortawesome/free-solid-svg-icons";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+
+import { SecretSyncConnectionField } from "@app/components/secret-syncs/forms/SecretSyncConnectionField";
+import { FilterableSelect, FormControl, Input, Tooltip } from "@app/components/v2";
+import { useHCVaultConnectionListMounts } from "@app/hooks/api/appConnections/hc-vault";
+import { SecretSync } from "@app/hooks/api/secretSyncs";
+
+import { TSecretSyncForm } from "../schemas";
+
+export const HCVaultSyncFields = () => {
+ const { control, setValue } = useFormContext<
+ TSecretSyncForm & { destination: SecretSync.HCVault }
+ >();
+
+ const connectionId = useWatch({ name: "connection.id", control });
+
+ const { data: mounts, isLoading: isMountsLoading } = useHCVaultConnectionListMounts(
+ connectionId,
+ {
+ enabled: Boolean(connectionId)
+ }
+ );
+
+ return (
+ <>
+ <SecretSyncConnectionField
+ onChange={() => {
+ setValue("destinationConfig.mount", "");
+ }}
+ />
+
+ <Controller
+ name="destinationConfig.mount"
+ control={control}
+ render={({ field: { onChange }, fieldState: { error } }) => (
+ <FormControl
+ isError={Boolean(error)}
+ errorText={error?.message}
+ label="Secrets Engine Mount"
+ helperText={
+ <Tooltip
+ className="max-w-md"
+ content="Ensure the Secrets Engine mount exists and that your App Role / Access Token has permission to access it. Infisical only supports version 2 KV Secrets Engine mounts."
+ >
+ <div>
+ <span>Don&#39;t see the mount you&#39;re looking for?</span>{" "}
+ <FontAwesomeIcon icon={faCircleInfo} className="text-mineshaft-400" />
+ </div>
+ </Tooltip>
+ }
+ >
+ <FilterableSelect
+ menuPlacement="top"
+ isLoading={isMountsLoading && Boolean(connectionId)}
+ isDisabled={!connectionId}
+ onChange={(option) =>
+ onChange((option as SingleValue<{ value: string }>)?.value ?? null)
+ }
+ options={mounts?.map((v) => ({ label: v, value: v }))}
+ placeholder="Select a Secrets Engine Mount..."
+ />
+ </FormControl>
+ )}
+ />
+ <Controller
+ name="destinationConfig.path"
+ control={control}
+ render={({ field: { value, onChange }, fieldState: { error } }) => (
+ <FormControl
+ tooltipClassName="max-w-sm"
+ tooltipText="The Secrets Engine mount path where secrets should be synced to."
+ isError={Boolean(error)}
+ errorText={error?.message}
+ label="Path"
+ >
+ <Input value={value} onChange={onChange} placeholder="dev/example" />
Greptile
greptile
logic: Path input needs sanitization against path traversal attacks. Consider validating against patterns like '../'
diff block
+import { request } from "@app/lib/config/request";
+import { getTeamCityInstanceUrl } from "@app/services/app-connection/teamcity";
+import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors";
+import { TSecretMap } from "@app/services/secret-sync/secret-sync-types";
+import {
+ TDeleteTeamCityVariable,
+ TPostTeamCityVariable,
+ TTeamCityListVariables,
+ TTeamCityListVariablesResponse,
+ TTeamCitySyncWithCredentials
+} from "@app/services/secret-sync/teamcity/teamcity-sync-types";
+
+// Note: Most variables won't be returned with a value due to them being a "password" type (starting with "env.").
+// TeamCity API returns empty string for password-type variables for security reasons.
+const listTeamCityVariables = async ({ instanceUrl, accessToken, project, buildConfig }: TTeamCityListVariables) => {
+ const { data } = await request.get<TTeamCityListVariablesResponse>(
+ buildConfig
+ ? `${instanceUrl}/app/rest/buildTypes/${buildConfig}/parameters`
+ : `${instanceUrl}/app/rest/projects/id:${project}/parameters`,
+ {
+ headers: {
+ Authorization: `Bearer ${accessToken}`,
+ Accept: "application/json"
+ }
+ }
+ );
+
+ // Strips out "env." from map key, but the "name" field still has the original unaltered key.
+ return Object.fromEntries(
+ data.property.map((variable) => [
+ variable.name.startsWith("env.") ? variable.name.substring(4) : variable.name,
+ { ...variable, value: variable.value || "" } // Password values will be empty strings from the API for security
+ ])
+ );
+};
+
+// Create and update both use the same method
+const updateTeamCityVariable = async ({
+ instanceUrl,
+ accessToken,
+ project,
+ buildConfig,
+ key,
+ value
+}: TPostTeamCityVariable) => {
+ return request.post(
+ buildConfig
+ ? `${instanceUrl}/app/rest/buildTypes/${buildConfig}/parameters`
+ : `${instanceUrl}/app/rest/projects/id:${project}/parameters`,
+ {
+ name: key,
+ value,
+ type: {
+ rawValue: "password display='hidden'"
+ }
+ },
+ {
+ headers: {
+ Authorization: `Bearer ${accessToken}`,
+ "Content-Type": "application/json"
+ }
+ }
+ );
+};
+
+const deleteTeamCityVariable = async ({
+ instanceUrl,
+ accessToken,
+ project,
+ buildConfig,
+ key
+}: TDeleteTeamCityVariable) => {
+ return request.delete(
+ buildConfig
+ ? `${instanceUrl}/app/rest/buildTypes/${buildConfig}/parameters/${key}`
+ : `${instanceUrl}/app/rest/projects/id:${project}/parameters/${key}`,
Greptile
greptile
logic: Key parameter in delete URL path is not escaped. Use encodeURIComponent to prevent path traversal attacks. ```suggestion buildConfig + ? `${instanceUrl}/app/rest/buildTypes/${encodeURIComponent(buildConfig)}/parameters/${encodeURIComponent(key)}` + : `${instanceUrl}/app/rest/projects/id:${encodeURIComponent(project)}/parameters/${encodeURIComponent(key)}`, ```
diff block
+import { request } from "@app/lib/config/request";
+import { getTeamCityInstanceUrl } from "@app/services/app-connection/teamcity";
+import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors";
+import { TSecretMap } from "@app/services/secret-sync/secret-sync-types";
+import {
+ TDeleteTeamCityVariable,
+ TPostTeamCityVariable,
+ TTeamCityListVariables,
+ TTeamCityListVariablesResponse,
+ TTeamCitySyncWithCredentials
+} from "@app/services/secret-sync/teamcity/teamcity-sync-types";
+
+// Note: Most variables won't be returned with a value due to them being a "password" type (starting with "env.").
+// TeamCity API returns empty string for password-type variables for security reasons.
+const listTeamCityVariables = async ({ instanceUrl, accessToken, project, buildConfig }: TTeamCityListVariables) => {
+ const { data } = await request.get<TTeamCityListVariablesResponse>(
+ buildConfig
+ ? `${instanceUrl}/app/rest/buildTypes/${buildConfig}/parameters`
+ : `${instanceUrl}/app/rest/projects/id:${project}/parameters`,
+ {
Greptile
greptile
logic: Project ID is not properly escaped in URL path. Use encodeURIComponent for project and buildConfig parameters to prevent path traversal. ```suggestion const { data } = await request.get<TTeamCityListVariablesResponse>( buildConfig + ? `${instanceUrl}/app/rest/buildTypes/${encodeURIComponent(buildConfig)}/parameters` + : `${instanceUrl}/app/rest/projects/id:${encodeURIComponent(project)}/parameters`, { ```
diff block
+const express = require('express');
+const path = require('path');
+const fs = require('fs');
+const compression = require('compression');
+
+const app = express();
+const PORT = process.env.PORT || 3000;
+
+// Use compression for all responses
+app.use(compression());
+
+// Serve static files from the build directory
+app.use(express.static(path.join(__dirname, 'build'), {
+ // Set caching headers
+ setHeaders: (res, filePath) => {
+ // For JavaScript and CSS files, cache for 1 week
+ if (filePath.endsWith('.js') || filePath.endsWith('.css')) {
+ res.setHeader('Cache-Control', 'public, max-age=604800');
+ }
+ // For HTML files, don't cache
+ else if (filePath.endsWith('.html')) {
+ res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
+ }
+ // For other assets, cache for 1 month
+ else {
+ res.setHeader('Cache-Control', 'public, max-age=2592000');
+ }
+ }
+}));
+
+// Serve the 404.html directly for /404 route (for testing)
+app.get('/404', (req, res) => {
+ res.sendFile(path.join(__dirname, 'build', '404.html'));
+});
+
+// For all other routes, serve index.html
+app.get('*', (req, res) => {
+ // Check if requested file exists in the build folder
+ const filePath = path.join(__dirname, 'build', req.path);
Greptile
greptile
logic: path.join with req.path could allow path traversal attacks. Use path.normalize and validate the path doesn't escape the build directory
suggested fix
+ const normalizedPath = path.normalize(req.path).replace(/^\.+/g, '');
+ const filePath = path.join(__dirname, 'build', normalizedPath);
+ if (!filePath.startsWith(path.join(__dirname, 'build'))) {
+ return res.status(403).send('Forbidden');
}
diff block
+import { AxiosError } from "axios";
+
+import { request } from "@app/lib/config/request";
+import { BadRequestError } from "@app/lib/errors";
+import { removeTrailingSlash } from "@app/lib/fn";
+import { blockLocalAndPrivateIpAddresses } from "@app/lib/validator";
+import { AppConnection } from "@app/services/app-connection/app-connection-enums";
+
+import { OnePassConnectionMethod } from "./1password-connection-enums";
+import { TOnePassConnection, TOnePassConnectionConfig, TOnePassVault } from "./1password-connection-types";
+
+export const getOnePassInstanceUrl = async (config: TOnePassConnectionConfig) => {
+ const instanceUrl = removeTrailingSlash(config.credentials.instanceUrl);
+
+ await blockLocalAndPrivateIpAddresses(instanceUrl);
+
+ return instanceUrl;
+};
+
+export const getOnePassConnectionListItem = () => {
+ return {
+ name: "1Password" as const,
+ app: AppConnection.OnePass as const,
+ methods: Object.values(OnePassConnectionMethod) as [OnePassConnectionMethod.ApiToken]
+ };
+};
+
+export const validateOnePassConnectionCredentials = async (config: TOnePassConnectionConfig) => {
+ const instanceUrl = await getOnePassInstanceUrl(config);
+
+ const { apiToken } = config.credentials;
+
+ try {
+ await request.get(`${instanceUrl}/v1/vaults`, {
Greptile
greptile
logic: URL concatenation with user input could be vulnerable to path traversal. Consider using URL constructor.
diff block
TCreateCaDTO,
TCreateCertificateDTO,
TCreateCertificateResponse,
+ TCreateUnifiedCertificateAuthorityDTO,
TDeleteCaDTO,
+ TDeleteUnifiedCertificateAuthorityDTO,
TImportCaCertificateDTO,
TImportCaCertificateResponse,
TRenewCaDTO,
TRenewCaResponse,
TSignIntermediateDTO,
TSignIntermediateResponse,
- TUpdateCaDTO
+ TUnifiedCertificateAuthority,
+ TUpdateCaDTO,
+ TUpdateUnifiedCertificateAuthorityDTO
} from "./types";
+export const useUpdateUnifiedCa = () => {
+ const queryClient = useQueryClient();
+ return useMutation<TUnifiedCertificateAuthority, object, TUpdateUnifiedCertificateAuthorityDTO>({
+ mutationFn: async ({ id, ...body }) => {
+ const {
+ data: { certificateAuthority }
+ } = await apiRequest.patch<{ certificateAuthority: TUnifiedCertificateAuthority }>(
+ `/api/v1/pki/ca/${body.type}/${id}`,
+ body
Greptile
greptile
logic: URL construction with user-controlled input (body.type) could be vulnerable to path traversal. Validate type parameter against allowed enum values before using in URL.
diff block
getCaEstConfig: (caId: string) => [{ caId }, "ca-est-config"]
};
+export const useGetCaByTypeAndId = (type: CaType, caId: string) => {
+ return useQuery({
+ queryKey: caKeys.getCaByTypeAndId(type, caId),
+ queryFn: async () => {
+ const {
+ data: { certificateAuthority }
+ } = await apiRequest.get<{ certificateAuthority: TUnifiedCertificateAuthority }>(
+ `/api/v1/pki/ca/${type}/${caId}`
+ );
Greptile
greptile
logic: URL path contains user-controlled input (type and caId) without proper validation. Could be vulnerable to path traversal attacks.
diff block
private static final Asciidoctor asciidoctor = create();
private static final String PREFIX = "doc:";
- private final Language language;
+ private final ResourceLoader resourceLoader;
- public AsciiDoctorTemplateResolver(Language language) {
- this.language = language;
+ public AsciiDoctorTemplateResolver(ResourceLoader resourceLoader) {
+ this.resourceLoader = resourceLoader;
setResolvablePatterns(Set.of(PREFIX + "*"));
}
@Override
protected ITemplateResource computeTemplateResource(IEngineConfiguration configuration, String ownerTemplate, String template, String resourceName, String characterEncoding, Map<String, Object> templateResolutionAttributes) {
var templateName = resourceName.substring(PREFIX.length());
- try (InputStream is = readInputStreamOrFallbackToEnglish(templateName, language)) {
- if (is == null) {
- log.warn("Resource name: {} not found, did you add the adoc file?", templateName);
- return new StringTemplateResource("");
- } else {
- JavaExtensionRegistry extensionRegistry = asciidoctor.javaExtensionRegistry();
- extensionRegistry.inlineMacro("webWolfLink", WebWolfMacro.class);
- extensionRegistry.inlineMacro("webWolfRootLink", WebWolfRootMacro.class);
- extensionRegistry.inlineMacro("webGoatVersion", WebGoatVersionMacro.class);
- extensionRegistry.inlineMacro("webGoatTempDir", WebGoatTmpDirMacro.class);
- extensionRegistry.inlineMacro("operatingSystem", OperatingSystemMacro.class);
- extensionRegistry.inlineMacro("username", UsernameMacro.class);
- StringWriter writer = new StringWriter();
- asciidoctor.convert(new InputStreamReader(is), writer, createAttributes());
- return new StringTemplateResource(writer.getBuffer().toString());
- }
- } catch (IOException e) {
- //no html yet
- return new StringTemplateResource("");
- }
- }
+ try (InputStream is = resourceLoader.getResource("classpath:/" + templateName).getInputStream()) {
Greptile
greptile
logic: Potential security risk - classpath resource loading without path validation could allow path traversal if templateName contains '../' sequences
suggested fix
+ if (templateName.contains("../")) {
+ throw new IllegalArgumentException("Invalid template name: " + templateName);
+ }
try (InputStream is = resourceLoader.getResource("classpath:/" + templateName).getInputStream()) {
diff block
package org.owasp.webgoat.webwolf;
+import jakarta.annotation.PostConstruct;
+import java.io.File;
+import org.owasp.webgoat.container.UserInterceptor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
-import javax.annotation.PostConstruct;
-import java.io.File;
-
/**
* @author nbaars
* @since 8/13/17.
*/
@Configuration
public class MvcConfiguration implements WebMvcConfigurer {
- @Value("${webwolf.fileserver.location}")
- private String fileLocation;
+ @Value("${webwolf.fileserver.location}")
+ private String fileLocation;
- @Override
- public void addResourceHandlers(ResourceHandlerRegistry registry) {
- registry.addResourceHandler("/files/**").addResourceLocations("file:///" + fileLocation + "/");
+ @Override
+ public void addResourceHandlers(ResourceHandlerRegistry registry) {
+ registry.addResourceHandler("/files/**").addResourceLocations("file:///" + fileLocation + "/");
Greptile
greptile
logic: Potential path traversal vulnerability by directly concatenating fileLocation into the resource path. Consider using Path.normalize() or similar sanitization.
suggested fix
+ registry.addResourceHandler("/files/**").addResourceLocations("file:///" + new File(fileLocation).toPath().normalize().toString() + "/");
diff block
@RequestMapping(value = "/requests")
public class Requests {
- private final WebWolfTraceRepository traceRepository;
- private final ObjectMapper objectMapper;
+ private final WebWolfTraceRepository traceRepository;
+ private final ObjectMapper objectMapper;
- @AllArgsConstructor
- @Getter
- private class Tracert {
- private final Instant date;
- private final String path;
- private final String json;
- }
+ @AllArgsConstructor
+ @Getter
+ private class Tracert {
+ private final Instant date;
+ private final String path;
+ private final String json;
+ }
- @GetMapping
- public ModelAndView get() {
- var model = new ModelAndView("requests");
- var user = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
- var traces = traceRepository.findAllTraces().stream()
- .filter(t -> allowedTrace(t, user))
- .map(t -> new Tracert(t.getTimestamp(), path(t), toJsonString(t))).collect(toList());
- model.addObject("traces", traces);
+ @GetMapping
+ public ModelAndView get(Authentication authentication) {
+ var model = new ModelAndView("requests");
+ String username = (null != authentication) ? authentication.getName() : "anonymous";
+ var traces =
+ traceRepository.findAll().stream()
+ .filter(t -> allowedTrace(t, username))
+ .map(t -> new Tracert(t.getTimestamp(), path(t), toJsonString(t)))
+ .toList();
+ model.addObject("traces", traces);
- return model;
- }
-
- private boolean allowedTrace(HttpTrace t, UserDetails user) {
- Request req = t.getRequest();
- boolean allowed = true;
- /* do not show certain traces to other users in a classroom setup */
- if (req.getUri().getPath().contains("/files") && !req.getUri().getPath().contains(user.getUsername())) {
- allowed = false;
- } else if (req.getUri().getPath().contains("/landing") && req.getUri().getQuery()!=null && req.getUri().getQuery().contains("uniqueCode") && !req.getUri().getQuery().contains(StringUtils.reverse(user.getUsername()))) {
- allowed = false;
- }
-
- return allowed;
- }
+ return model;
+ }
- private String path(HttpTrace t) {
- return (String) t.getRequest().getUri().getPath();
+ private boolean allowedTrace(HttpExchange t, String username) {
+ HttpExchange.Request req = t.getRequest();
+ boolean allowed = true;
+ /* do not show certain traces to other users in a classroom setup */
+ if (req.getUri().getPath().contains("/files") && !req.getUri().getPath().contains(username)) {
+ allowed = false;
Greptile
greptile
logic: Path traversal vulnerability - username should be sanitized before using in path comparison
diff block
return imagePath;
},
);
+
+ ipcMain.handle(
+ MainChannels.DELETE_IMAGE_FROM_PROJECT,
+ async (_event, projectRoot: string, imageName: string) => {
+ const imagePath = await deleteImageFromProject(projectRoot, imageName);
+ return imagePath;
+ },
+ );
+
+ ipcMain.handle(
+ MainChannels.RENAME_IMAGE_IN_PROJECT,
+ async (_event, projectRoot: string, imageName: string, newName: string) => {
+ const imagePath = await renameImageInProject(projectRoot, imageName, newName);
+ return imagePath;
Greptile
greptile
logic: No validation that newName is a valid filename. Could allow injection of path traversal characters.
suggested fix
async (_event, projectRoot: string, imageName: string, newName: string) => {
+ if (newName.includes('/') || newName.includes('\\')) {
+ throw new Error('Invalid filename: must not contain path separators');
+ }
const imagePath = await renameImageInProject(projectRoot, imageName, newName);
return imagePath;
diff block
+package org.owasp.webgoat.lessons.pathtraversal;
+
+import static org.owasp.webgoat.container.assignments.AttackResultBuilder.failed;
+import static org.owasp.webgoat.container.assignments.AttackResultBuilder.success;
+
+import jakarta.annotation.PostConstruct;
+import jakarta.servlet.http.HttpServletRequest;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.file.Files;
+import java.util.Base64;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.RandomUtils;
+import org.owasp.webgoat.container.CurrentUsername;
+import org.owasp.webgoat.container.assignments.AssignmentEndpoint;
+import org.owasp.webgoat.container.assignments.AssignmentHints;
+import org.owasp.webgoat.container.assignments.AttackResult;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.core.token.Sha512DigestUtils;
+import org.springframework.util.FileCopyUtils;
+import org.springframework.util.StringUtils;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@AssignmentHints({
+ "path-traversal-profile-retrieve.hint1",
+ "path-traversal-profile-retrieve.hint2",
+ "path-traversal-profile-retrieve.hint3",
+ "path-traversal-profile-retrieve.hint4",
+ "path-traversal-profile-retrieve.hint5",
+ "path-traversal-profile-retrieve.hint6"
+})
+@Slf4j
+public class ProfileUploadRetrieval implements AssignmentEndpoint {
+ private final File catPicturesDirectory;
+
+ public ProfileUploadRetrieval(@Value("${webgoat.server.directory}") String webGoatHomeDirectory) {
+ this.catPicturesDirectory = new File(webGoatHomeDirectory, "/PathTraversal/" + "/cats");
+ this.catPicturesDirectory.mkdirs();
+ }
+
+ @PostConstruct
+ public void initAssignment() {
+ for (int i = 1; i <= 10; i++) {
+ try (InputStream is =
+ new ClassPathResource("lessons/pathtraversal/images/cats/" + i + ".jpg")
+ .getInputStream()) {
+ FileCopyUtils.copy(is, new FileOutputStream(new File(catPicturesDirectory, i + ".jpg")));
+ } catch (Exception e) {
+ log.error("Unable to copy pictures" + e.getMessage());
+ }
+ }
+ var secretDirectory = this.catPicturesDirectory.getParentFile().getParentFile();
+ try {
+ Files.writeString(
+ secretDirectory.toPath().resolve("path-traversal-secret.jpg"),
+ "You found it submit the SHA-512 hash of your username as answer");
+ } catch (IOException e) {
+ log.error("Unable to write secret in: {}", secretDirectory, e);
+ }
+ }
+
+ @PostMapping("/PathTraversal/random")
+ @ResponseBody
+ public AttackResult execute(
+ @RequestParam(value = "secret", required = false) String secret,
+ @CurrentUsername String username) {
+ if (Sha512DigestUtils.shaHex(username).equalsIgnoreCase(secret)) {
+ return success(this).build();
+ }
+ return failed(this).build();
+ }
+
+ @GetMapping("/PathTraversal/random-picture")
+ @ResponseBody
+ public ResponseEntity<?> getProfilePicture(HttpServletRequest request) {
+ var queryParams = request.getQueryString();
+ if (queryParams != null && (queryParams.contains("..") || queryParams.contains("/"))) {
+ return ResponseEntity.badRequest()
+ .body("Illegal characters are not allowed in the query params");
+ }
Greptile
greptile
logic: Path traversal check is insufficient - only checks query string but not the final constructed file path. An attacker could still traverse directories using encoded characters or other techniques.
diff block
+import { isValidUrl, youtubeParser } from '../utils'
+import { ResizeHandle, XStack } from '../../ui/src'
+import { useEffect, useState } from 'react'
+import { Block, BlockNoteEditor, defaultProps } from '@/lib/blocknote'
+import { createReactBlockSpec } from '@/lib/blocknote'
+import { MediaContainer } from '../media-container'
+import { DisplayComponentProps, MediaRender, MediaType } from '../media-render'
+import { HMBlockSchema } from '../../schema'
+
+export const getSourceType = (name: string) => {
+ const nameArray = name.split('.')
+ return nameArray[nameArray.length - 1] ? `video/${nameArray[nameArray.length - 1]}` : undefined
+}
+
+export const VideoBlock = createReactBlockSpec({
+ type: 'video',
+ propSchema: {
+ ...defaultProps,
+ url: {
+ default: '',
+ },
+ src: {
+ default: '',
+ },
+ name: {
+ default: '',
+ },
+ width: {
+ default: '',
+ },
+ defaultOpen: {
+ values: ['false', 'true'],
+ default: 'false',
+ },
+ },
+ containsInlineContent: true,
+ // @ts-ignore
+ render: ({ block, editor }: { block: Block<HMBlockSchema>; editor: BlockNoteEditor<HMBlockSchema> }) =>
+ Render(block, editor),
+
+ parseHTML: [
+ {
+ tag: 'video[src]',
+ getAttrs: (element: any) => {
+ return { url: element.getAttribute('src') }
+ },
+ },
+ {
+ tag: 'iframe',
+ getAttrs: (element: any) => {
+ return { url: element.getAttribute('src') }
+ },
+ },
+ ],
+})
+
+const Render = (block: Block<HMBlockSchema>, editor: BlockNoteEditor<HMBlockSchema>) => {
+ const submitVideo = async (
+ assignMedia: (props: MediaType) => void,
+ queryType: string,
+ url?: string,
+ setErrorRaised?: any,
+ ) => {
+ if (queryType === 'upload') {
+ const filePaths = await window.fileSystem.openVideoFileDialog()
+
+ if (filePaths && filePaths.length > 0) {
+ const filePath: string = filePaths[0]
+ const fileData = await window.fileSystem.readFile(filePath, 'base64')
+ const videoData = `data:video/mp4;base64,${fileData}`
+
+ const storedVideoUrl = await window.fileSystem.storeVideo(videoData, filePath, block.id)
+ console.log(`storedVideoURL: ${storedVideoUrl}`)
+ assignMedia({
+ id: block.id,
+ props: {
+ url: storedVideoUrl,
+ name: filePath,
+ },
+ children: [],
+ content: [],
+ type: 'video',
+ })
+ }
+ } else if (url && isValidUrl(url)) {
+ let embedUrl = 'https://www.youtube.com/embed/'
+ if (url.includes('youtu.be') || url.includes('youtube')) {
+ let ytId = youtubeParser(url)
+ if (ytId) {
+ embedUrl = embedUrl + ytId
+ } else {
+ setErrorRaised(`Unsupported Youtube URL`)
+ return
+ }
+ } else if (url.includes('vimeo')) {
+ const urlArray = url.split('/')
+ embedUrl = `https://player.vimeo.com/video/${urlArray[urlArray.length - 1]}`
+ } else {
Greptile
greptile
logic: Vimeo ID extraction is unsafe - could allow path traversal if URL is malformed. Add URL validation before splitting.
diff block
+import { HogFunctionTemplate } from '../../types'
+
+export const template: HogFunctionTemplate = {
+ free: true,
+ status: 'alpha',
+ type: 'transformation',
+ id: 'template-pii-hashing',
+ name: 'PII Data Hashing',
+ description:
+ 'This transformation hashes sensitive personal data (PII) like email, phone numbers, etc. using SHA-256 to protect user privacy.',
+ icon_url: '/static/hedgehog/builder-hog-02.png',
+ category: ['Custom'],
+ hog: `
+// Get the properties to hash from inputs
+let propertiesToHash := inputs.propertiesToHash
+if (empty(propertiesToHash)) {
+ return event
+}
+
+// Create a copy of the event to modify
+let returnEvent := event
+
+// Hash each property value
+for (let _, path in propertiesToHash) {
+ let value := event.properties[path]
Greptile
greptile
logic: Using event.properties[path] directly could fail if path contains nested properties (e.g. 'user.email'). Need to implement proper property path traversal.
diff block
+=== Path traversal while uploading files
+
+In this assignment, the goal is to overwrite a specific file on the file system. Of course, WebGoat cares about the users
+so you need to upload your file to the following location outside the usual upload location.
Greptile
greptile
style: The wording 'WebGoat cares about the users' followed by instructions for a path traversal attack could be confusing for students learning about security vulnerabilities
diff block
+=== Zip Slip vulnerability
+
+As a developer, you have many occasions where you have to deal with zip files. For example, think about the upload facility or processing a bunch of CSV files that are uploaded as a zip file. A neat vulnerability was discovered and responsibly disclosed by the Snyk Security team. It uses path traversal, which can be used while extracting files. With the path traversal, you try to overwrite files outside the intended target folder. For example, you might be able to overwrite the `ls` command while extracting a zip file. Once this command has been replaced with some extra malicious actions each time the user types in `ls`, you can send the outcome of the listing towards your server before showing the actual command to the user. So you end up with remote command execution.
+
+==== Problem
+
+The problem occurs with how we extract zip files in Java; a common way to do this is:
+
+[source]
+----
+File destinationDir = new File("/tmp/zip");
+Enumeration<? extends ZipEntry> entries = zip.entries();
+while (entries.hasMoreElements()) {
+ ZipEntry e = entries.nextElement();
+ File f = new File(destinationDir, e.getName());
Greptile
greptile
logic: This line shows the core vulnerability - no validation is performed on e.getName() before creating the file, allowing path traversal
diff block
+use chirp_workflow::prelude::*;
+
+pub fn user_avatar(config: &rivet_config::Config, user: &crate::types::User) -> String {
+ if let (Some(upload_id), Some(file_name)) =
+ (user.profile_upload_id, user.profile_file_name.as_ref())
+ {
+ format!(
+ "{}/media/user-avatar/{}/{}",
+ util::url::to_string_without_slash(
+ &config.server().unwrap().rivet.api_public.public_origin()
+ ),
+ upload_id,
+ file_name
+ )
Greptile
greptile
style: URL path components (upload_id and file_name) should be properly URL-encoded to prevent path traversal attacks and handle special characters
suggested fix
"{}/media/user-avatar/{}/{}",
util::url::to_string_without_slash(
&config.server().unwrap().rivet.api_public.public_origin()
),
+ url::form_urlencoded::byte_serialize(upload_id.as_bytes()).collect::<String>(),
+ url::form_urlencoded::byte_serialize(file_name.as_bytes()).collect::<String>()
)
diff block
+import { UnrecoverableError } from 'bullmq';
+
+import { FileService } from 'src/engine/core-modules/file/services/file.service';
+import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator';
+import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator';
+import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
+
+export type FileDeletionJobData = {
+ workspaceId: string;
+ fullPath: string;
+};
+
+@Processor(MessageQueue.workspaceQueue)
+export class FileDeletionJob {
+ constructor(private readonly fileService: FileService) {}
+
+ @Process(FileDeletionJob.name)
+ async handle(data: FileDeletionJobData): Promise<void> {
+ const { workspaceId, fullPath } = data;
+
+ const folderPath = fullPath.split('/').slice(0, -1).join('/');
+ const filename = fullPath.split('/').pop();
Greptile
greptile
style: Check for empty or malformed paths before splitting to prevent potential issues with path traversal
diff block
@Value("${server.port}")
private int port;
- @RequestMapping(path = "/tmpdir", consumes = ALL_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
+ @RequestMapping(path = "/file-server-location", consumes = ALL_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
@ResponseBody
public String getFileLocation() {
return fileLocation;
}
- @PostMapping(value = "/WebWolf/fileupload")
+ @PostMapping(value = "/fileupload")
public ModelAndView importFile(@RequestParam("file") MultipartFile myFile) throws IOException {
- WebGoatUser user = (WebGoatUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
- File destinationDir = new File(fileLocation, user.getUsername());
+ var user = (WebGoatUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
+ var destinationDir = new File(fileLocation, user.getUsername());
destinationDir.mkdirs();
myFile.transferTo(new File(destinationDir, myFile.getOriginalFilename()));
Greptile
greptile
style: No validation on file name or content type before saving, potential path traversal vulnerability
suggested fix
+ destinationDir.mkdirs();
+ String sanitizedFileName = new File(myFile.getOriginalFilename()).getName();
+ myFile.transferTo(new File(destinationDir, sanitizedFileName));
diff block
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.RedirectView;
-import javax.servlet.http.HttpServletRequest;
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-
-import static org.springframework.http.MediaType.ALL_VALUE;
-
-/**
- * Controller for uploading a file
- */
+/** Controller for uploading a file */
@Controller
@Slf4j
public class FileServer {
- @Value("${webwolf.fileserver.location}")
- private String fileLocation;
- @Value("${server.address}")
- private String server;
- @Value("${server.port}")
- private int port;
-
- @RequestMapping(path = "/file-server-location", consumes = ALL_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
- @ResponseBody
- public String getFileLocation() {
- return fileLocation;
+ private static final DateTimeFormatter dateTimeFormatter =
+ DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+
+ @Value("${webwolf.fileserver.location}")
+ private String fileLocation;
+
+ @Value("${server.address}")
+ private String server;
+
+ @Value("${server.servlet.context-path}")
+ private String contextPath;
+
+ @Value("${server.port}")
+ private int port;
+
+ @RequestMapping(
+ path = "/file-server-location",
+ consumes = ALL_VALUE,
+ produces = MediaType.TEXT_PLAIN_VALUE)
+ @ResponseBody
+ public String getFileLocation() {
+ return fileLocation;
+ }
+
+ @PostMapping(value = "/fileupload")
+ public ModelAndView importFile(
+ @RequestParam("file") MultipartFile multipartFile, Authentication authentication)
+ throws IOException {
+ var username = authentication.getName();
+ var destinationDir = new File(fileLocation, username);
+ destinationDir.mkdirs();
+ // DO NOT use multipartFile.transferTo(), see
+ // https://stackoverflow.com/questions/60336929/java-nio-file-nosuchfileexception-when-file-transferto-is-called
+ try (InputStream is = multipartFile.getInputStream()) {
+ var destinationFile = destinationDir.toPath().resolve(multipartFile.getOriginalFilename());
+ Files.deleteIfExists(destinationFile);
Greptile
greptile
logic: Path traversal vulnerability - filename is not sanitized before resolving against the destination path
suggested fix
+ var sanitizedFilename = new File(multipartFile.getOriginalFilename()).getName(); // Get just the filename part
+ var destinationFile = destinationDir.toPath().resolve(sanitizedFilename);
Files.deleteIfExists(destinationFile);
diff block
package org.owasp.webgoat.lessons.xxe;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.PrintWriter;
import lombok.extern.slf4j.Slf4j;
-import org.owasp.webgoat.container.session.WebSession;
-import org.springframework.beans.factory.annotation.Autowired;
+import org.owasp.webgoat.container.CurrentUsername;
import org.springframework.beans.factory.annotation.Value;
+import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.PrintWriter;
-
@Slf4j
public class Ping {
- @Value("${webgoat.user.directory}")
- private String webGoatHomeDirectory;
- @Autowired
- private WebSession webSession;
+ @Value("${webgoat.user.directory}")
+ private String webGoatHomeDirectory;
- @RequestMapping(method = RequestMethod.GET)
- @ResponseBody
- public String logRequest(@RequestHeader("User-Agent") String userAgent, @RequestParam(required = false) String text) {
- String logLine = String.format("%s %s %s", "GET", userAgent, text);
- log.debug(logLine);
- File logFile = new File(webGoatHomeDirectory, "/XXE/log" + webSession.getUserName() + ".txt");
- try {
- try (PrintWriter pw = new PrintWriter(logFile)) {
- pw.println(logLine);
- }
- } catch (FileNotFoundException e) {
- log.error("Error occurred while writing the logfile", e);
- }
- return "";
+ @GetMapping
+ @ResponseBody
+ public String logRequest(
+ @RequestHeader("User-Agent") String userAgent,
+ @RequestParam(required = false) String text,
+ @CurrentUsername String username) {
+ String logLine = String.format("%s %s %s", "GET", userAgent, text);
+ log.debug(logLine);
+ File logFile = new File(webGoatHomeDirectory, "/XXE/log" + username + ".txt");
Greptile
greptile
logic: Path traversal vulnerability: concatenating user input (username) directly into file path without sanitization
diff block
+import fs from "fs";
+import path from "path";
+import os from "os";
+
+/**
+ * Retrieves the content of a specified context file.
+ * Returns the content if the file exists, otherwise returns an error message.
+ */
+export default async function (input: {
+ /**
+ * The name of the context file to read (without .txt extension)
+ */
+ name: string;
Greptile
greptile
logic: Add input validation to prevent path traversal via malicious context names
suggested fix
name: string;
+ if (input.name.includes('/') || input.name.includes('\\')) {
+ return 'Invalid context name';
+ }