whoami

Lucas Jenß

cat /etc/motd

The Coding Journal ツ — Notes taken on an epic coding journey. Technical solutions, debugging notes, and practical guides from the trenches of software development.

Close-up of white dominoes with black dots standing on green felt surface, shallow depth of field, focused mood

ls -la ~/languages/

total 8
drwxr-xr-x
▶ PHP
▶ Ruby
▶ Scala
▶ C#
▶ JavaScript
▶ Objective-C
▶ Shell Scripting

ls -la ~/toolchain/

total 7
drwxr-xr-x
▶ Typo3
▶ Akka
▶ Capistrano
▶ Git
▶ MAMP
▶ Adobe Illustrator
▶ NSTrackingArea (Cocoa)

uname -a

platforms
drwxr-xr-x
▶ Mac OS X
▶ Unix

Securing TYPO3 extensions against PHP directory traversal attacks

TYPO3 powers a surprising slice of the Australian web, from Brisbane council sites to Sydney health portals and Melbourne-based university intranets. A custom extension that mishandles user-supplied file paths can quickly become a vector for directory traversal — letting attackers read system files, dump database credentials, or overwrite cached templates. For developers operating under the Privacy Act 1988, getting this wrong is not merely embarrassing; it can trigger mandatory reporting to the Office of the Australian Information Commissioner.

The good news is that TYPO3 ships with thoughtful primitives for safe file handling, and PHP itself provides the functions needed to validate paths before they reach the filesystem. The walk-through below focuses on practical patterns I have applied in real extensions — the same patterns that keep The Coding Journal running on an Australian VPS without incident.

How directory traversal actually works

A traversal payload looks innocent at first glance. An attacker submits something like ../../../../etc/passwd, ..%2F..%2Fconfig.php, or — in a unicode-normalised form — ..%c0%af..%c0%afwp-config.php. When that string is concatenated into a filesystem call such as file_get_contents() or fopen(), the PHP runtime happily follows the relative segments and returns a file the extension was never meant to expose.

In a TYPO3 context the input often arrives through Extbase actions, piVars, or AJAX endpoints registered in ext_localconf.php. A download action that joins a tx_myext_pi1[file] parameter directly into GeneralUtility::getFileAbsFileName() is a classic offender. PHP's include, require, and the various readfile() flavours behave the same way once the path resolves outside the intended directory. Empty results, 403 responses, and unexpectedly large log lines are often the first signal that someone has been probing for this weakness.

Common attack surfaces in custom extensions

Most traversal flaws in TYPO3 extensions cluster around four patterns. First, file download or preview actions that take a filename as a controller argument. Second, template-rendering features that pull files by relative path from fileadmin/. Third, log viewers or debug panels that accept a path to expose. Fourth, import/export routines that read CSV or XML payloads from a user-supplied location.

Each of those features is useful, even necessary, in a content-heavy Australian deployment — a museum uploading exhibitions, a regional tourism board reissuing brochures, or a small accounting firm exporting client statements. The risk lies not in the feature itself but in the absence of a normalisation step. When you build the controller, treat every incoming string as untrusted. That single habit prevents most of the headlines about travel sites being compromised through a banner-image downloader.

Validating and sanitising file paths

The first defence is to reject obvious traversal characters before the path ever reaches a filesystem call. Reject any input containing .., encoded variants of .., null bytes, or backslashes on a Unix deployment. PHP's strpos() against the raw string is a quick pre-filter, but it is not sufficient on its own — the real safety net is to resolve the path through realpath() and compare the result against a whitelist.

The table below compares the most common PHP techniques and where each fits in a defence-in-depth model.

Technique What it stops Limits When to use it
strpos($input, '..') Literal ../ segments Misses encoded, unicode, and Windows-style traversal First-pass filter in controllers
realpath() + prefix match All resolved escapes, including symlink hops Relies on the file existing on disk Before any read/write happens
basename() on user input Trailing segments that walk up Does not stop .. in the middle Filename-only parameters
Whitelist via in_array() Everything outside the allow-list Maintenance overhead, brittle if list changes Known set of templates or assets
TYPO3 ResourceFactory Path resolution through FAL storage registry Requires the file to live in a registered storage Default for media in Extbase

Combining basename() for the trailing component with realpath() for the joined path is usually enough for a download endpoint. Add a whitelist when the set of legitimate files is finite.

Realpath and whitelisting strategies

A robust helper in a TYPO3 extension follows three steps: normalise the user input, join it to a known root directory, and then resolve and verify. The root should always be a configuration constant, never a runtime concatenation that an attacker can influence. Inside an Extbase controller this often means injecting the storage path through dependency injection rather than reading it from the request.

A whitelist makes sense whenever the extension exposes a known catalogue of files — say, the publicly available fact sheets from a regulator or the brochure PDFs from a tourism operator. The list can live in a TypoScript setting, a database table, or a simple YAML fixture, but it must be authoritative. If a file is not on the list, the controller returns a 404 and writes a structured log entry. This keeps the surface area small and makes auditing trivial during the next compliance review.

Always call realpath() on both the joined path and the root, then compare with strncmp() or str_starts_with() on PHP 8+. Direct equality checks fail on case-insensitive filesystems, but a prefix check tolerates the legitimate subdirectories you actually want to expose.

Using the File Abstraction Layer correctly

TYPO3's File Abstraction Layer is purpose-built for this kind of problem. Routes that serve media should accept a FAL identifier rather than a raw path, then load the file through \TYPO3\CMS\Core\Resource\ResourceFactory. FAL resolves identifiers against registered file storages, refuses lookups outside the configured base directory, and integrates with the file permission masks that you can tune per user group.

Custom extensions sometimes bypass FAL because the legacy code they wrap pre-dates TYPO3 9. Migrating those modules is tedious but worthwhile, particularly when the deployment serves dual purposes like a council intranet that also exposes public PDFs. With FAL in place, the worst a malicious parameter can do is request a file that already lives in the storage — which is generally the desired behaviour anyway.

Hardening the deployment context

Code-level fixes only matter if the surrounding environment cooperates. Disable allow_url_fopen and allow_url_include in php.ini, both of which prevent a class of remote-file-inclusion traps that often accompany traversal probes. Set open_basedir to the web root plus the typo3temp directory, and rely on the operating system user running PHP-FPM to prevent reads outside that boundary.

For Australian hosts, confirm that your provider — whether it runs from a Sydney data centre, a Melbourne cloud region, or a Brisbane-managed cluster — follows the Essential Eight maturity model and offers log retention of at least 90 days. Local expectations around data sovereignty continue to tighten, particularly for government-funded projects, and a hosting partner familiar with local compliance regimes can save weeks of paperwork later.

Logging, monitoring and incident response

Even a well-hardened extension benefits from a paper trail. Log every rejected path with the request URI, the user identifier, and the offending payload. Forward those entries to a central SIEM so an analyst can correlate spikes — repeated probes are often precursors to a broader compromise. When a suspicious request escapes the filter and touches a sensitive file, lock the account, rotate the affected credentials, and follow the statutory assessment window required by Australian privacy law before notifying the OAIC.

For practical tips on chasing down related misconfigurations, the walk-through at RADware licensing notes covers a complementary class of deployment bugs that often hide in the same infrastructure footprint as traversal flaws.

If you ship TYPO3 extensions for Australian clients, treat directory traversal as a baseline requirement rather than a feature. Add a checklist to your pull-request template, gate merges on realpath coverage, and rehearse the incident response once a quarter — the few hours you spend now are far cheaper than a published breach notice and the regulator correspondence that follows.


cat ~/interests.json

KeyValue
editorTerminal-first workflow
osMac OS X / Unix
vcsGit, distributed version control
deployCapistrano, cron automation
graphicsSVG, Adobe Illustrator troubleshooting
networkingIP validation, SSH, VPN

git log --oneline --reverse

2013-10-30

Solving SVG import issues in Adobe Illustrator CS6 and CC

When importing an SVG into Illustrator, the operation fails with an unknown error [CANT]. A workaround for this Adobe-side bug.

2013

Solving NDK build issues on OS X

Troubleshooting native development kit compilation problems on Mac OS X.

2013

Programmatically adding PHP generated TypoScript to the backend configuration

Integrating dynamically generated TypoScript into Typo3 backend setups using PHP.

2013

ArgumentError: Could not parse PKey: no start line

Debugging an SSH key parsing error encountered during deployment.

2011-08-04

Validating IP-Addresses in PHP

Using PHP filter functions with flags like FILTER_FLAG_IPV4 and FILTER_FLAG_IPV6, and understanding how filter_var handles reserved IP addresses.

2011-07-09

Cocoa: Using NSTrackingArea

A short tutorial on using Cocoa's NSTrackingArea to capture mouseEntered and mouseExited events.


cat ~/contact.txt

github: github.com/x3ro
stackoverflow: x3ro
coderwall: coderwall.com/x3ro
twitter: @x3rames