🧠 Path Traversal

What is it?

  • Concept: The server used does not sanitize user input or uses a flawed filtering/sanitizing logic that allow malicious actor to traverse through the local file system.
  • Impact: It is a way to achieve arbitrary file access (either read or write or execute - aka. LFI).

Exploitations

Common Obfuscation

  1. URL Encodings:
  • . becomes %2e (or %2E) and %252E (double encoding)
  • / becomes %2f (or %2F) and %252F (double encoding)
  • \ becomes %5c (or %5C) - Useful for Windows backends

URL Encode:

# ../
%2e%2e%2f
 
# example
GET /image?file=%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd

Double URL Encode:

# ../
%252E%252E%252F
 
#example
GET /image?file=%252E%252E%252F%252E%252E%252F%252E%252E%252Fetc%252Fpasswd
  1. The ....// bypass
# example
GET /image?file=....//....//....//etc/passwd

PHP Object Hex Obfuscation

Sometimes the path is inside a PHP object and the server filter it using something likestripos($input, '..'), you can obfuscate your injected path inside the object with HEX-encoding using the S: flag inside the object.

S: flag indicates that the field is a string but unlike its lowercase version, S: accepts input as HEX-encoding when the object is being deserialized (like being passed to unserialized()).

Common HEX-encoding of charaters related to path traversal:

  • . becomes \2e
  • / becomes \2f
  • \ becomes \5c
  • \0 null bytes becomes \00

Javascript string.includes() Bypass

Javascript has a rather annoying function called includes() that gives you a boolean whether the input string contains a certain substring or not, a famous example of such a usage is the HTB - Next Path challenge where input.includes("/") || input.includes("..") was implemented in order to stop Path Traversal.

However, in Javascript array also has its own includes() methods that check whether a certain ELEMENTS is being contained inside the array or not.

const array1 = ["123", "/"];
console.log(array1.includes("/")); 
// output: true 
 
const array2 = ["123", "../../etc/passwd"];
console.log(array2.includes("/")); 
// output: false 

If a POST endpoints allows you to input a array inside a json body you can do something like this to inject and array:

{
	"Message":["item1", "item2"]
}

As for GET endpoints you can sometimes parse an array by using duplicate parameters (i.e. ?id=&id=...). This is called Parameter Pollution if the back-end does not handle this correctly. Here are some Framework/Library that supports this:

Takes duplicates parameters as Array:

  • Express.js (Node.js)
  • Koa.js (Node.js)
  • Ruby on Rails

Only takes the First parameters as value:

  • Python (Flask / Werkzeug): To get the array, the developer specifically has to know to call request.args.getlist('id')
  • Modern Javascript (URLSearchParams): Use URLSearchParams.getAll('id') to get array input.

Only takes the Last parameters as value:

  • PHP: use the brackets syntax?id[]= to get an array input.

Mitigation

  • Normalization before Validation: Always normalize the input path into its real form, collapses all path traversal, then check it with a white-list before actually validate the path.
  • Indirect Object Reference: If possible, never allow user input a path in the first place, instead, map the filenames to a database id, user can only input the id, the database looks it up, and serves the file at the corresponding location.
  • Strict Type Checking: To prevent parameter poisoning, we must strictly verify that the input is a string
  • Use Schema Validation: Route the incoming request data through a schema validator like Zod or Joi, etc. If the schema defines the input is a string, inputting an array using duplicate parameters will be rejected.
  • Stop Allow Passing An Unserialized Object: Bro, just use Json, please. Passing a serialized object is dangerous, and using unserialize() is also dangerous. If you are using PHP version 7.0.0, please update to version 8, so that things like (incomplete) PHP Archive Deserialization can never exist and do path traversal.
TABLE creation_date AS "Created" 
FROM "05 - Content" 
WHERE contains(techniques, this.file.link) AND contains(tags, "🚩") 
SORT file.name ASC