Untrust Us.
cd deploy && docker compose up --build -d
Let's describe some milestones.
Client-side problems are straightforward:
There is obvious prototype pollution in Context.ContextProvider:
const context: any = { name, setName };
const previous: string = decodeURIComponent(window.location.hash.slice(1));
JSON.parse(previous || "[]").map(([x, y, z]: any[]) => context[x][y] = z);So we can control location.hash value and arbitrary pollute object.
ViewMessage page downloads html from the server and inserts it using dangerouslySetInnerHTML.
components/pages/ViewMessagePage/index.tsx
return (
<div className='ViewMessagePage'>
<div className='ViewMessagePage-Header'>
<span className='ViewMessagePage-Title'>Excess | Message</span>
</div>
<div className='ViewMessagePage-Container'>
<Error error={error}/>
<div className='ViewMessagePage-Message' dangerouslySetInnerHTML={{__html: html}}></div>
<div className='ViewMessagePage-Buttons'>
<Button onClick={backClickHandler} id='back' text='Back'/>
</div>
</div>
</div>
);
The server handles API exceptions using custom exception handler.
void Api::HandleException(const httplib::Request& req, httplib::Response& res, const std::exception_ptr ptr) {
std::string error;
try {
std::rethrow_exception(ptr);
} catch (const BadRequestError& ex) {
error = ex.what();
res.status = httplib::StatusCode::BadRequest_400;
} catch (const Storage::MessageAlreadyExistsError& ex) {
error = ex.what();
res.status = httplib::StatusCode::Conflict_409;
} catch (const Services::InvalidSessionError& ex) {
error = ex.what();
res.status = httplib::StatusCode::Unauthorized_401;
} catch (const Services::InvalidCredentialsError& ex) {
error = ex.what();
res.status = httplib::StatusCode::Unauthorized_401;
} catch (const Services::MessageNotFoundError& ex) {
error = ex.what();
res.status = httplib::StatusCode::NotFound_404;
} catch (const std::exception& ex) {
error = ex.what();
}
nlohmann::json result = {
{"error", error},
};
res.set_content(result.dump(), JsonContentType);
}But there are two problems:
During registration the server checks if the new author already exists.
void SqliteStorage::CreateAuthor(const Models::Author& author) {
auto sql = "insert into authors (name, password) values (?, ?)"s;
try {
ExecuteSql(sql, { author.GetName(), author.GetPassword() });
} catch (const SqliteConflictError&) {
throw AuthorAlreadyExistsError("author " + author.GetName() + " already exists"s);
}
}But AuthorAlreadyExistsError has no catch clause for itself. Instead it will be proceed as std::exception default clause. Note that it doesn't set res.status, so it would be 200_OK.
What if another exception occured during handling the exception? Then function Api::HandleException will throw this exception and server will crash. Note that there is no check for JSON exceptions.
nlohmann::json result = {
{"error", error},
};
res.set_content(result.dump(), JsonContentType);So if JSON will throw the exception the server will crash.
-
Use prototype pollution to pollute
headersandmethodfields of object. It leads to controlfetch()parameters object and allows us to perform any request. -
Use
Range: bytes=17-header in order to download a part of returned JSON. Basically if the server setres.statusit's not possible, but onAuthorAlreadyExistsErrorexceptionres.statusis not set and range header will be applied -
Use XS-leak to exfiltrate flag. CSP blocks inline javascript, so we can't use
<script>, but we still can insert HTML. Use object with lazy loading fallback.
<object data='URL'>
<img src='FALLBACK_URL' loading='lazy'>
</object>If call to URL returns error then FALLBACK_URL will be called. If URL returns 200 OK there won't be any call to FALLBACK_URL.
-
Throw unhandled exception if prefix is not correct. Set
URLto/messages?content=<prefix>andFALLBACK_URLto/message/%ff. JSON will throw an exception during\xffserialization, it leads to server downtime. -
Track server downtime from internet. We know public URL so we can easily perform many requests and check is the server down.
So the final chain:
1. pollute fetch headers
2. conflict on /register -> html inserted on the page
3. call to /messages?content=<prefix> with fallback to /message/%ff
4. check if the server is crashed
Example solver: solution/exploit.html