Webhook Abuse in Aspnet with Bearer Tokens
Webhook Abuse in Aspnet with Bearer Tokens — how this specific combination creates or exposes the vulnerability
Webhook abuse in ASP.NET applications often intersects with bearer token authentication when token handling is misconfigured, exposing endpoints to unauthorized invocation or token leakage. A webhook is a user-defined HTTP callback, typically a POST to a public URL; when that URL is protected only by a bearer token that is embedded in client-side code, stored insecurely, or transmitted without strict validation, the attack surface expands.
Consider an ASP.NET Core controller that accepts webhook events and expects an Authorization header with a bearer token. If the token is static, hard‑coded, or shared across clients, an attacker who discovers the token can forge requests that the server treats as legitimate. For example, a client might send events to /api/webhook/stripe with header Authorization: Bearer whsec_abc123. If the token is leaked—through logs, error messages, or referer headers—or if the endpoint does not verify the token per‑client, an attacker can replay captured requests, inject malicious payloads, or trigger business logic such as refunds or notifications.
Another common pattern is token confusion across services. An ASP.NET app might call an external API using a bearer token obtained via a client credential flow, but if the webhook handler reuses the same token for both inbound validation and outbound calls without scoping or audience checks, an attacker who can influence the webhook payload might coerce the server into making privileged calls to downstream APIs. This can lead to privilege escalation or data exfiltration when combined with insecure deserialization or missing input validation.
Insecure transport and weak token storage amplify the risk. If the webhook endpoint accepts HTTP instead of HTTPS, tokens can be intercepted. Even with HTTPS, if the ASP.NET app does not validate the token audience (aud) or issuer (iss) claims, a token intended for another resource might be accepted. OWASP API Security Top 10 items such as Broken Object Level Authorization (BOLA) and Improper Assets Management intersect here: webhook URLs and tokens may be inadvertently exposed in client-side code or public repositories, making them enumerable and reusable.
Real-world exploitation chains often involve SSRF or insecure consumption. An attacker might trick an ASP.NET server into sending webhook notifications to internal metadata services, using a captured bearer token to escalate to cloud provider metadata APIs. Alternatively, they might manipulate webhook events to include malicious serialized objects if input validation is weak, leading to injection or remote code execution despite the presence of a bearer token.
To detect such issues, scans should validate that each webhook endpoint enforces per‑client token validation, uses short‑lived tokens, binds tokens to specific scopes and audiences, and rejects requests with malformed or missing Authorization headers. Cross‑reference OpenAPI specs to ensure token requirements are documented and runtime tests confirm that mismatched or missing tokens are rejected with appropriate HTTP status codes.
Bearer Tokens-Specific Remediation in Aspnet — concrete code fixes
Remediation centers on strict token validation, avoiding hard‑coded secrets, and ensuring tokens are used with appropriate scope and audience. Below are concrete ASP.NET Core examples that demonstrate secure handling.
1. Validate Bearer Tokens via Authentication Middleware
Use ASP.NET Core’s built‑in JWT Bearer authentication to validate tokens before requests reach your webhook handler.
// Program.cs or Startup.cs
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = "https://your-identity-provider.com";
options.Audience = "webhook-api";
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = "https://your-identity-provider.com",
ValidateAudience = true,
ValidateLifetime = true,
ClockSkew = TimeSpan.FromMinutes(2)
};
});
app.UseAuthentication();
app.UseAuthorization();
This ensures only tokens signed by your identity provider, intended for the webhook-api audience, and not expired are accepted.
2. Per‑Client Token Binding in Webhook Endpoints
Instead of a single shared token, associate each webhook subscription with its own token and validate it explicitly in the handler.
[ApiController]
[Route("api/webhook/[controller]")]
public class StripeWebhookController : ControllerBase
{
private readonly IWebhookRepository _repository;
public StripeWebhookController(IWebhookRepository repository)
{
_repository = repository;
}
[HttpPost]
public async Task<IActionResult> Receive()
{
if (!Request.Headers.TryGetValue("Authorization", out var authHeader))
return Unauthorized(new { error = "Missing Authorization header" });
var token = authHeader.ToString().Replace("Bearer ", string.Empty);
var isValid = await _repository.ValidateWebhookTokenAsync(Request.Path, token);
if (!isValid)
return Unauthorized(new { error = "Invalid token" });
using var reader = new StreamReader(Request.Body);
var body = await reader.ReadToEndAsync();
// Process event securely
return Ok();
}
}
ValidateWebhookTokenAsync should compare the token against a per‑webhook secret stored server‑side, ideally encrypted, and bound to the webhook ID and scope.
3. Avoid Token Reuse and Enforce Short Lifetimes
When your app obtains bearer tokens for downstream calls, do not reuse the same token for inbound webhook validation. Issue short‑lived tokens with limited scope and include the aud claim specific to the target endpoint.
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[]
{
new Claim(JwtRegisteredClaimNames.Sub, "webhook-client"),
new Claim(JwtRegisteredClaimNames.Scope, "webhook.send"),
new Claim("webhook_id", webhookId.ToString())
}),
Expires = DateTime.UtcNow.AddMinutes(5),
SigningCredentials = signingCredentials,
Audience = "webhook-api",
Issuer = "your-service"
};
var token = new JwtSecurityTokenHandler().CreateToken(tokenDescriptor);
var tokenString = new JwtSecurityTokenHandler().WriteToken(token);
Rotate secrets regularly and revoke tokens on subscription changes. Combine with anti‑CSRF measures for webhook registration forms and enforce HTTPS for all webhook traffic.
Frequently Asked Questions
How can I prevent webhook token leakage in logs and error messages?
builder.Logging.AddFilter("Microsoft.AspNetCore.HttpLogging", LogLevel.Warning). Sanitize exceptions and do not include request headers in error responses.