Urgent: Ongoing Automated Attack Exploiting SoundCloud OAuth Callback
Description:
If your deepsound website is currently under an automated attack targeting the SoundCloud OAuth callback endpoint (/deepsound-login.php). Despite user registration being disabled globally, attackers are exploiting a security flaw in this endpoint to mass-inject fake user accounts directly into the database.
The vulnerable script accepts callback requests without CSRF state validation or IP-based rate limiting. It generates fake email addresses (e.g., ds_[username]@soundcloud.com) and creates a new user record for each request.
Database Impact:
Thousands of fake records are being inserted into the T_USERS table every hour.
The database size is increasing rapidly, affecting query performance.
Fake accounts are identifiable by email pattern (%@soundcloud.com) and missing soundcloud_id fields.
Server resources (CPU, disk I/O) are under heavy load due to continuous write operations.
Urgent Action Required: Immediate patching of the callback endpoint and cleanup of malicious records.
The EASIEST solution would be: Using Cloudflare Turnstile is currently the most efficient way to prevent this type of automated attack with minimal interference to real users (it is often invisible and doesn’t require users to click on images).
Since the SoundCloud Callback is a GET request that carries the authorization code directly in the URL parameters, we cannot verify Turnstile directly within this initial request (because Turnstile requires frontend rendering and a POST submission).
Solution: Adopt an "Intermediary Page" Interception Strategy.
We need to modify this PHP file to handle two steps:
Step 1 (Interception): When SoundCloud redirects back with the code, do not write to the database immediately. Instead, display a simple page with Cloudflare Turnstile and hide the code in a hidden form field.
Step 2 (Verification & Execution): When the user passes the human verification (Turnstile) and the form is submitted, the backend verifies the Turnstile Token. Only after successful verification do we execute the original registration/login logic.
This does not require changing any database structure.
Modified PHP Code Scheme
Please clear your original script/deepsound-login.php entirely and replace it with the following. Make sure to fill in your own Cloudflare Site Key and Secret Key in the configuration area at the top.
<?php
// 1. Initialize System
require_once('./assets/init.php');
// --- Configuration Area (Get these from Cloudflare Dashboard) ---
$cf_site_key = 'YOUR_CLOUDFLARE_SITE_KEY';
$cf_secret_key = 'YOUR_CLOUDFLARE_SECRET_KEY';
// ----------------------------------------
// If the user is already logged in, or the feature is disabled, redirect immediately.
if (IS_LOGGED || $music->config->soundcloud_login == 'on') {
header("Location: $site_url");
exit;
}
// 2. Logic Branches
// [Branch A]: Handle Turnstile Verification (POST Request)
// This runs after the user passes the check on the intermediary page.
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['cf-turnstile-response'])) {
// A1. Verify Cloudflare Turnstile Server-Side
$turnstile_response = $_POST['cf-turnstile-response'];
$remote_ip = $_SERVER['REMOTE_ADDR'];
$verify_url = 'https://challenges.cloudflare.com/turnstile/v0/siteverify';
$data = [
'secret' => $cf_secret_key,
'response' => $turnstile_response,
'remoteip' => $remote_ip
];
$options = [
'http' => [
'header' => "Content-type: application/x-www-form-urlencoded\r\n",
'method' => 'POST',
'content' => http_build_query($data)
]
];
$context = stream_context_create($options);
$verify_result = file_get_contents($verify_url, false, $context);
$verify_json = json_decode($verify_result);
// A2. Verification Failed
if ($verify_json->success === false) {
exit("Security Check Failed (Turnstile Error). Please try again.");
}
// A3. Verification Success -> Execute original SoundCloud Login/Register logic
// Note: Here we use the 'code' from the POST data, not the GET data.
if (!isset($_POST['original_code'])) {
exit("Missing SoundCloud Code.");
}
$client_id = $music->config->sound_cloud_client_id;
$client_secret = $music->config->sound_cloud_client_secret;
$redirect_uri = $music->config->site_url . "/deepsound-login.php";
try {
$code = $_POST['original_code']; // Retrieve the code from the hidden input
$method = 1;
$header = 0;
$json = 1;
$url = "https://api.soundcloud.com/oauth2/token";
$data = array(
"client_id" => $client_id,
"client_secret" => $client_secret,
"redirect_uri" => $redirect_uri,
"grant_type" => "authorization_code",
"code" => $code
);
$get_access_token = http_request_call($method, $url, $header, $data, $json);
if (empty($get_access_token['access_token'])) {
// If the code is expired or reused, this error prevents replay attacks.
exit("SoundCloud Token Error: Please login again.");
}
$access_token = $get_access_token['access_token'];
$get_user_info = file_get_contents("https://api.soundcloud.com/me?oauth_token=$access_token");
$result = json_decode($get_user_info, true);
// --- Original User Creation/Login Logic Starts Here ---
$provider = 'soundcloud';
$name = $result['full_name'];
$avatar_url = $result['avatar_url'];
$user_name = 'ds_' . $result['permalink'];
$user_email = $user_name . '@soundcloud.com';
$str = md5(microtime());
$id = substr($str, 0, 9);
$password = substr(md5(time()), 0, 9);
$user_uniq_id = (empty($db->where('username', $id)->getValue(T_USERS, 'id'))) ? $id : 'u_' . $id;
if (EmailExists($user_email) === true) {
$db->where('email', $user_email);
$login = $db->getOne(T_USERS);
createUserSession($login->id);
header("Location: $site_url");
exit();
} else {
// Optional: You can still add the global registration switch check here if needed.
// if ($music->config->user_registration == 'off') { exit(); }
$re_data = array(
'username' => secure($user_uniq_id, 0),
'email' => secure($user_email, 0),
'password' => secure(sha1($password), 0),
'email_code' => secure(sha1($user_uniq_id), 0),
'name' => secure($name),
'avatar' => secure(importImageFromLogin($avatar_url)),
'src' => secure($provider),
'active' => '1'
);
$re_data['language'] = $music->config->language;
if (!empty($_SESSION['lang'])) {
if (in_array($_SESSION['lang'], $langs)) {
$re_data['language'] = $_SESSION['lang'];
}
}
$insert_id = $db->insert(T_USERS, $re_data);
if ($insert_id) {
createUserSession($insert_id);
header("Location: $site_url");
exit();
}
}
// --- Original Logic Ends Here ---
} catch (Exception $e) {
exit($e->getMessage());
}
}
// [Branch B]: Initial State - SoundCloud just redirected back (GET Request)
// Display the "Security Check" page
else if (isset($_GET['code'])) {
$original_code = $_GET['code'];
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Security Check</title>
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
<style>
body { font-family: sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; background: #f0f2f5; }
.card { background: white; padding: 2rem; border-radius: 8px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); text-align: center; }
h3 { margin-top: 0; color: #333; }
</style>
</head>
<body>
<div class="card">
<h3>One more step...</h3>
<p>Please verify you are human to complete the login.</p>
<form action="" method="POST" id="verify-form">
<input type="hidden" name="original_code" value="<?php echo htmlspecialchars($original_code); ?>">
<div class="cf-turnstile"
data-sitekey="<?php echo $cf_site_key; ?>"
data-callback="javascriptCallback"></div>
</form>
</div>
<script>
// Automatically submit the form when verification is successful
function javascriptCallback(token) {
document.getElementById("verify-form").submit();
}
</script>
</body>
</html>
<?php
exit(); // Stop script execution, wait for user submission
} else {
// Neither POST (validation) nor GET (callback), redirect to home
header("Location: $site_url");
exit();
}
?>
````
Code Principle & Defense Logic
Blocking Attack Scenarios:
Before: An attacker obtains a SoundCloud callback URL (or forges one) and uses a script to ping this URL repeatedly. Your server would constantly query the SoundCloud API and attempt to write to the database.
Now: When an attacker (bot) visits this URL, they only receive a static HTML page. Since scripts cannot execute JavaScript or solve the complex cryptographic challenges of Cloudflare Turnstile, the attack stops there. PHP does not execute any database operations.
Hidden Input Transfer:
The code provided by SoundCloud has an expiration time. We temporarily store it using <input type="hidden" name="original_code" ...>.
PHP only retrieves this code to execute the actual login after the user passes Turnstile and the form is POSTed.
Security:
Even if an attacker attempts to forge a POST request, since they cannot generate a valid cf-turnstile-response (which requires a real browser environment), the server-side validation ($verify_json->success) will fail, and the script will exit immediately.
**How to Implement**
Go to the Cloudflare Dashboard -> Turnstile -> Add Site.
Obtain your Site Key and Secret Key.
Fill these two keys into the $cf_site_key and $cf_secret_key variables in the code above.
Save the file.
This effectively adds a "security door" to your SoundCloud login interface that bots cannot pass.