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.
ls -la ~/languages/
- drwxr-xr-x
- ▶ PHP
- ▶ Ruby
- ▶ Scala
- ▶ C#
- ▶ JavaScript
- ▶ Objective-C
- ▶ Shell Scripting
ls -la ~/toolchain/
- drwxr-xr-x
- ▶ Typo3
- ▶ Akka
- ▶ Capistrano
- ▶ Git
- ▶ MAMP
- ▶ Adobe Illustrator
- ▶ NSTrackingArea (Cocoa)
uname -a
- 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
| Key | Value |
|---|---|
| editor | Terminal-first workflow |
| os | Mac OS X / Unix |
| vcs | Git, distributed version control |
| deploy | Capistrano, cron automation |
| graphics | SVG, Adobe Illustrator troubleshooting |
| networking | IP validation, SSH, VPN |
git log --oneline --reverse
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.
Solving NDK build issues on OS X
Troubleshooting native development kit compilation problems on Mac OS X.
Programmatically adding PHP generated TypoScript to the backend configuration
Integrating dynamically generated TypoScript into Typo3 backend setups using PHP.
ArgumentError: Could not parse PKey: no start line
Debugging an SSH key parsing error encountered during deployment.
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.
Cocoa: Using NSTrackingArea
A short tutorial on using Cocoa's NSTrackingArea to capture mouseEntered and mouseExited events.
cat ~/contact.txt