Multiple Vulnerabilities in Idno – Known PHP CMS software
Introduction
My journey into Source code security auditing started back in October 2021, when I was creating an intentionally vulnerable Linux machine for online hacking. I had to audit and modify the PHP source code of a popular WordPress Plugin to create a payload for an exploitable vulnerability of my machine. Next, in December 2021 a Recruiter provided me with a challenge to find vulnerabilities in two files of C# code. Honestly, back then I didn’t knew what C# is and had never seen any C# code in my life before. But I accepted the challenge anyways and to my amazement I was able to find multiple vulnerabilities in the first page of C# code within two hours!! The second page of code was dead simple and the vulnerability was pretty clear, but the Recruiter suggested that there are actually multiple vulnerabilities in that page. So, I tried harder and after a few hours of reading the official C# docs I was able to find two more vulnerabilities in the code. I went from not knowing about the existence of a programming language to finding multiple vulnerabilities in its code within two days!
Although the Recruiter didn’t hire me, his test gave me a lesson that’s far more valuable, i.e I learned to always believe in myself and my abilities. I figured out that If I can find vulnerabilities in the code of an obscure language like C#, then I can easily find vulnerabilities in more popular languages like PHP which has a clear cut syntax and is extremely easy to read and understand. Next, after a couple of months I was introduced to Secure Code Warrior which further boosted my confidence in Source code security auditing. And today, I’m disclosing multiple vulnerabilities in a popular PHP CMS project on Github. Also, as the vendor didn’t respond to my multiple attempts at contacting them, these vulnerabilities are not patched yet, which is why I’m only going to show the vulnerable source code and the remediation action that must be taken and will not be disclosing any of the PoC that I’d submitted to MITRE. So, without further ado, let’s get started.
Table of Contents
- CVE-2022-33011 :- Idno Known version <=1.3.1 is vulnerable to Account Takeover via Password Reset Poisoning through HTTP Host header injection attack.
- Credits
- Coordinated Disclosure Attempts
- Conclusion
The Vendor
Known is a social publishing platform owned by the guys at https://withknown.com/. The have some badass tight coding practices. Their SQL injection prevention coding methodology is top notch and even though I was able to produce a classic MySQL error, I was not able to turn that into an exploit. When I looked into their code, I saw some really tight security practices to prevent SQL injection. I think some of the vulnerabilities below are more of an oversight by the Known Team rather than bad security practices.
CVE-2022-30852 :- Known version <= 1.3.1 is vulnerable to Insecure Direct Object Reference(IDOR).
Timeline
10/05/2022 – Found the vulnerability and submitted a CVE request to MITRE.
16/05/2022 – CVE Record created.
10/06/2022 – Got mail from MITRE about three vulnerabilities(submitted on different dates) getting a CVE ID allocated.
11/06/2022 – Sent first mail to vendor mentioning about the vulnerabilities.
28/06/2022 – Sent third mail mentioning four vulnerabilities.
04/07/2022 – Publicly disclosed the vulnerability without PoC.
The Vulnerability
While reading the code for pages that allow access to admin panel to a normal user, I found one page which allows any logged-in user to access certain settings of admin panel that allows to turn off content being displayed on the Site frontend. The vulnerable functions are getContent() and postContent() of Homepage class under the namespace Idno\Pages\Admin. While every other settings page under the Admin panel is secured with the adminGatekeeper() function which ensures only the site Administrators can access the admin panel, the Homepage class is not. This is the adminGatekeeper() code:-
/**
* Placed in pages to ensure that only logged-in site administrators can
* get at them. Sets response code 401 and tries to forward
* to the front page.
*/
function adminGatekeeper()
{
$ok = false;
if (\Idno\Core\Idno::site()->session()->isLoggedIn()) {
if (\Idno\Core\Idno::site()->session()->currentUser()->isAdmin()) {
$ok = true;
}
}
if (!$ok) {
$this->deniedContent();
}
}
Now, every settings page in Admin panel is secured using the adminGatekeeper() function except one and that page allows any logged-in user to access the settings. The vulnerable code is as follows:-
I’m sorry but as this vulnerability is not patched yet, I’m unable to disclose more details. Also, as this vulnerability is extremely easy to exploit, I used images instead of the original source code to make it slightly harder for anyone to copy and search, not that it’d stop anyone determined to exploit the vulnerability, but still it’s something.
The Remediation
To be honest, this vulnerability is easiest to patch out of the four vulnerabilities. Just replace
$this->createGatekeeper with $this->adminGatekeeper.
My Github pull request for the above vulnerability was approved by the vendor and the code changes can be viewed through this link.
Afterthought
I’d say this vulnerability is more of an oversight than insecure coding practice. I think there are two possible reasons why such a mistake happened:-
- The person writing the code for other admin panel pages could be different than the one writing the code for the vulnerable page.
- Maybe the person writing the code thought that he is writing code for the homepage on the frontend and as such restricted the settings in admin panel to logged-in users only.
CVE-2022-31290 :- Idno Known version <= 1.3.1 is vulnerable to multiple Stored Cross Site Scripting(XSS) attacks.
Timeline
16/05/2022 – Found the vulnerability and submitted a CVE request to MITRE.
23/05/2022 – CVE Record created.
10/06/2022 – Got mail from MITRE about three vulnerabilities(submitted on different dates) getting a CVE ID allocated.
11/06/2022 – Sent first mail to vendor mentioning about the vulnerabilities.
28/06/2022 – Sent third mail mentioning four vulnerabilities.
04/07/2022 – Publicly disclosed the vulnerabilities.
The Vulnerability
This vulnerability can be split into two categories:-
- Stored XSS without Admin Privileges.
- Stored XSS with Admin Privileges.
Stored XSS without Admin Privileges.
The vulnerable code is :-
In the above code you can see that the “name” input field is vulnerable to Stored XSS. They’re using the secure htmlspecialchars() function but instead of validating the user supplied input they are trying to fetch some user details using the ‘user’ variable.
Stored XSS with Admin Privileges.
The vulnerable pieces of code are:-
The problem is same as above but a bit serious as the two input fields shown above affect the entire site. The htmlspecialchars() function was used but instead of validating user supplied input it is applied on a value that was pre-determined and was being fetched from the config.
The Remediation
The remediation is easy but would depend upon the devs what they want to do. For example, they can implement something like this:-
<?php echo htmlspecialchars($request->getParsedBody()['user-input'], ENT_QUOTES); ?>
Instead of validating a predetermined value, the user supplied input can be validated by parsing the HTTP request body for user input and then passing the value to the htmlspecialchars() function. I think Guzzle HTTP, which is a dependency of Known, has something similar.
CVE-2022-32115 :- Idno Known version <= 1.3.1 is vulnerable to arbitrary Javascript execution due to improper SVG validation.
Timeline
30/05/2022 – Submitted a CVE request to MITRE.
31/05/2022 – CVE Record created.
10/06/2022 – Got mail from MITRE about three vulnerabilities(submitted on different dates) getting a CVE ID allocated.
11/06/2022 – Sent first mail to vendor mentioning about the vulnerabilities.
28/06/2022 – Sent third mail mentioning four vulnerabilities.
04/07/2022 – Publicly disclosed the vulnerability without PoC.
The Vulnerability
So, the story goes like this:- I was looking for file upload vulnerabilities for almost two days and was going through their code which handles all image file related issues. Suddenly, I saw something that made me very happy. It was this piece of code:-
/**
* Given a file and an original file path, determines whether this file is an SVG
*
* @param $file_path
* @return bool
*/
public static function isSVG($file_path, $original_file_path)
{
if (pathinfo($original_file_path, PATHINFO_EXTENSION) == 'svg') {
return true; // TODO better SVG validation would be nice
}
return false;
}
On my first look, I didn’t considered this function anything interesting as many PHP projects that I’ve tested create a function at one place but do validation on it at another place. I was going to move on to read the rest of the code, but suddenly something in this code struck me! Guess what? The friendly “TODO” note left by the devs. 🙂
// TODO better SVG validation would be nice
I was going hard on the code for two days and this one line lit me up! No freaking SVG validation!! That’s like a hacker’s goldmine! As a reference, I’m leaving you guys this guide which will show you what’s possible with arbitrary SVG file uploads. Guess what’s the next thing I do? I went straight to the frontend and uploaded a classic SVG XSS payload file and by just clicking on the file I got XSS! But this vulnerability was in the latest Github release of idno/known software and not in the version that was supported by security updates. I know right, you might be thinking Latest release was not supported by security updates? Well, ask the devs about that. The problem now was, in the dev version(supported by security updates) of Known my previous payload was not working even though the isSVG() function was still the same as above. I quickly ran a diff against the two files and found this code was added:-
/**
* Detects whether the file contains PHP or script tags, eg to check for embedded code in GIFs
* @param $file_path
* @return bool
*/
public static function isFileFreeFromScriptTags($file_path)
{
if ($contents = file_get_contents($file_path)) {
if (stripos($contents, '<script') || stripos($contents, '<?php')) return false;
return true;
}
return false;
}
So, now they’ve implemented script tags validation. But, if you’ve a attacker’s mindset like me, then you can immediately see that the above code is still not enough to prevent XSS. Although, the above code prevents another vulnerability that I found in a few other PHP CMS webapps, it didn’t prevent the vulnerability that I got the CVE for! After trying out a couple of payloads, I was able to bypass the above protection.
The Remediation
Better SVG validation would be nice! 🙂 More seriously though, the best option in my opinion would be to disable SVG files support altogether. I mean more than 90% vendors in the world have dropped SVG files support from their webapp. Now, it’s entirely upto the Known team whether they would like to support SVG files or not. But, if you look clearly at the timeline of this vulnerability above, you’ll notice that MITRE created a CVE record for this vulnerability within one day! The problem here is not just XSS, there are a lot of other vulnerabilities that can be exploited via this vector! Also, another tip for the Known team, using filters like <?php won’t stop an attacker as they can still use PHP short_open_tag to bypass the filter. I’d say keep the above filter code as it is(it prevents some other vulnerabilities) and just add more filters to it or if the Known team is not against added dependencies, then maybe use some library that does the heavy lifting for them.
CVE-2022-33011 :- Idno Known version <=1.3.1 is vulnerable to Account Takeover via Password Reset Poisoning through HTTP Host header injection attack.
Timeline
08/06/2022 – Submitted a CVE request to MITRE.
13/06/2022 – CVE Record created.
28/06/2022 – Got mail from MITRE mentioning that a CVE ID is allocated.
28/06/2022 – Sent third mail mentioning four vulnerabilities including this one.
04/07/2022 – Publicly disclosed the vulnerability.
The Vulnerability
The vulnerable code is :-
/**
* Attempt to detect your known configuration's server name.
*/
protected function detectBaseURL()
{
// Otherwise, use the standard server name header
if (!empty($_SERVER['SERVER_NAME'])) {
// Servername specified, so we can construct things in the normal way.
$url = (\Idno\Common\Page::isSSL() ? 'https://' : 'http://') . $_SERVER['SERVER_NAME'];
if (!empty($_SERVER['HTTP_X_FORWARDED_PORT'])) {
if ($_SERVER['HTTP_X_FORWARDED_PORT'] != 80 && $_SERVER['HTTP_X_FORWARDED_PORT'] != 443) {
$url .= ':' . $_SERVER['HTTP_X_FORWARDED_PORT'];
}
} else if (!empty($_SERVER['SERVER_PORT'])) {
if ($_SERVER['SERVER_PORT'] != 80 && $_SERVER['SERVER_PORT'] != 443) {
$url .= ':' . $_SERVER['SERVER_PORT'];
}
}
if (defined('KNOWN_SUBDIRECTORY')) {
$url .= '/' . KNOWN_SUBDIRECTORY;
}
$url .= '/'; // A naive default base URL
return $url;
}
$domain = getenv('KNOWN_DOMAIN');
if (!empty($domain)) {
// Server domain specified in environment variable, for cases when SERVER_NAME isn't specified.
// This allows things like the console plugins to access the correct site when using domain
// specific configurations.
$url = (\Idno\Common\Page::isSSL() ? 'https://' : 'http://') . $domain;
$port = getenv('KNOWN_PORT');
if (!$port) {
$port = 80;
}
if ($port != 80 && $port != 443) {
$url .= ':' . $port;
}
if (defined('KNOWN_SUBDIRECTORY')) {
$url .= '/' . KNOWN_SUBDIRECTORY;
}
$url .= '/'; // A naive default base URL
return $url;
}
// No servername set, try something else
// TODO: Detect servername using other methods (but don't use HTTP_HOST)
// Default to root relative urls
return '/';
}
Now that you know about the vulnerability can you detect from the above code where the vulnerability actually is? The funny thing is that the devs again left for us another nice TODO mentioning that there can be certain cases where ServerName may not be set which could lead to HTTP Host header injection. Now, the mention of “but don’t use HTTP_HOST” shows that the devs are completely aware of the risks of HTTP Host header injection attacks, but what I think they’re not aware of is that the “SERVER_NAME” element of $_SERVER variable can also be vulnerable if some checks are not implemented.
The UseCanonicalName is an important directive in cases when Apache needs to create self-referential URLs, which is exactly the case of this vulnerability. I’ve not checked with Nginx but my test with Apache2 webserver shows that it is vulnerable to HTTP Host header injection attack. By the way, as you can see from the image above, PHP themselves are against relying on “SERVER_NAME” value for security. Note, some security blogs mention explicitly to use “SERVER_NAME” over “HTTP_HOST” but none of them mentions the above fact that the “SERVER_NAME” element can also be vulnerable.
The Remediation
My recommendation would be to save the “domain name” of the Known server somewhere in a config file and then refer to that “domain name” value from the config file for creating password reset URLs instead of depending on the HTTP Host header to create password reset URLs. I think this is the easiest way to prevent the Password Reset Poisoning attack and in my experience I’ve seen a few other PHP CMS webapps use this technique. Also, the guys from PortSwigger has some other neat recommendations, which I think could be useful for the Known devs.
Credits
As I’m the sole person responsible for finding these vulnerabilities, all credit obviously goes to me. 🙂
Coordinated Disclosure Attempts
I made three attempts to contact the Known Security Team according to their security policy on Github.
The last REMINDER was sent on 28/06/2022 and today is 04/07/2022 and I still don’t have any response from them. I could’ve created an issue on Github but their security policy clearly mentions to report only to their Security Email. As they clearly mentioned that they’ll reply within five days of sending email and still they didn’t replied back, I finally decided to go ahead and publish the vulnerabilities.
Conclusion
Source code security auditing is definitely no child’s game. It takes time and helluva lot of patience. I think I got lucky with some of the vulnerabilities above but still I toiled for some of the others. In the end, it was a very rewarding experience and one which will definitely come handy if I get an AppSec job. Now, once I get an Infosec job, the first thing I’m going to do is thank that C# recruiter for his simple test. Sometimes simple things in life can go a long way. It’s almost 5 AM in the morning here and I’m giving my finishing touch to this blog post. Hope, someone finds this useful. And as always, thanks for reading. Peace!
It’s perfect time to makе a few plans fоr the longer term аnd it is
time to be happy. I’ve read this post and I would say you make some interesting issues οr suggestions.
Μaybe yoս can write subsequent articles regаrding tһis article.
I ᴡish to гead even more things aboսt it!
Thanks. I’ve plans to write similar articles in the future.
There is no version 1.3.1, where did you get to see that version?
It’s the dev version that is supported by security updates according to their security policy.