On August 4th, the LedgerSMB project was advised of a security vulnerability in the code. Please see below our security advisory.
Reflected cross-site scripting of authenticated users in LedgerSMB
Summary: ========
LedgerSMB does not sufficiently HTML-encode error messages sent to the browser. By sending a specially crafted URL to an authenticated user, this flaw can be abused for remote code execution and information disclosure.
Known vulnerable: =================
All of:
- 1.1.0 upto 1.1.12 (including) - 1.2.0 upto 1.2.26 (including) - 1.3.0 upto 1.3.47 (including) - 1.4.0 upto 1.4.42 (including) - 1.5.0 upto 1.5.30 (including) - 1.6.0 upto 1.6.33 (including) - 1.7.0 upto 1.7.32 (including) - 1.8.0 upto 1.8.17 (including)
Known fixed: ============
- 1.7.33 - 1.8.18
Details: ========
When encountering an error, LedgerSMB sends the user feedback which may include user-provided input. This input was not sufficiently sanitized before being included in the error report. This allows an attacker inject a script in the error response page by send a specially crafted URL to an authenticated user. As the error page itself does not contain any sensitive information, a sophisticated payload in addition to targetting a sufficiently privileged user, is required for information disclosure.
Proper audit control and separation of duties limit Integrity impact of the attack vector.
The vulnerable code to provide this user-feedback dates back to version 1.0. Please note that not error messages are vulnerable to this attack as not all messages report the problematic input to the user.
Severity: =========
CVSSv3.1 Base Score: 8.2 (High)
CVSSv3.1 Vector: AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:L/A:N
Recommendations: ================
We recommend all users to upgrade to known-fixed versions. Versions prior to 1.7 are end-of-life and will not receive security fixes from the LedgerSMB project.
Users who cannot upgrade, may apply the included patches or are advised to contact a vendor for custom support.
There are no workarounds available for this vulnerability.
References: ===========
CVE-2021-3694 (LedgerSMB)
https://ledgersmb.org/cve-2021-3694-cross-site-scripting
https://huntr.dev/bounties/ef7f4cf7-3a81-4516-b261-f5b6ac21430c/
Reported by: ============
ranjit-git, user of the huntr.dev platform
Patches: ========
Patch for LedgerSMB 1.8 (1.8.0 upto 1.8.17, including): -------------------------------------------------------
[[[ diff --git a/lib/LedgerSMB/PSGI/Util.pm b/lib/LedgerSMB/PSGI/Util.pm index 11c01918f..bf443d886 100644 --- a/lib/LedgerSMB/PSGI/Util.pm +++ b/lib/LedgerSMB/PSGI/Util.pm @@ -24,6 +24,7 @@ use strict; use warnings;
use Carp; +use HTML::Escape; use HTTP::Status qw( HTTP_OK HTTP_INTERNAL_SERVER_ERROR HTTP_SEE_OTHER HTTP_BAD_REQUEST );
@@ -41,7 +42,7 @@ Returns a standard error representation for HTTP status 500
sub internal_server_error { - my ($msg, $title, $company, $dbversion) = @_; + my ($msg, $title, $company, $dbversion) = map { escape_html($_ // '') } @_;
$title //= 'Error!'; $msg =~ s/\n/<br>/g; diff --git a/old/lib/LedgerSMB/oldHandler.pm b/old/lib/LedgerSMB/oldHandler.pm index 1db966406..848eeb75c 100644 --- a/old/lib/LedgerSMB/oldHandler.pm +++ b/old/lib/LedgerSMB/oldHandler.pm @@ -57,6 +57,7 @@ use LedgerSMB::Sysconfig;
use Cookie::Baker; use Digest::MD5; +use HTML::Escape; use Log::Log4perl; use Try::Tiny;
@@ -184,14 +185,17 @@ sub handle { sub _error { my ($form, $msg, $status) = @_; $msg = "? _error" if !defined $msg; + my $html_msg = escape_html($msg); + my $html_dbversion = escape_html($form->{dbversion}); + my $html_company = escape_html($form->{company}); $status = 500 if ! defined $status;
print qq|Status: $status ISE Content-Type: text/html; charset=utf-8
<html> -<body><h2 class="error">Error!</h2> <p><b>$msg</b></p> -<p>dbversion: $form->{dbversion}, company: $form->{company}</p> +<body><h2 class="error">Error!</h2> <p><b>$html_msg</b></p> +<p>dbversion: $html_dbversion, company: $html_company</p> </body> </html> |; ]]]
Patch for LedgerSMB 1.7 (1.7.0 upto 1.7.32, including): -------------------------------------------------------
[[[ diff --git a/lib/LedgerSMB/PSGI/Util.pm b/lib/LedgerSMB/PSGI/Util.pm index 2d6195d69..b716a01c4 100644 --- a/lib/LedgerSMB/PSGI/Util.pm +++ b/lib/LedgerSMB/PSGI/Util.pm @@ -24,6 +24,7 @@ use strict; use warnings;
use Carp; +use HTML::Escape; use HTTP::Status qw( HTTP_OK HTTP_INTERNAL_SERVER_ERROR HTTP_SEE_OTHER HTTP_UNAUTHORIZED );
@@ -41,7 +42,7 @@ Returns a standard error representation for HTTP status 500
sub internal_server_error { - my ($msg, $title, $company, $dbversion) = @_; + my ($msg, $title, $company, $dbversion) = map { escape_html($_ // '') } @_;
$title //= 'Error!'; $msg =~ s/\n/<br>/g; diff --git a/old/bin/old-handler.pl b/old/bin/old-handler.pl index 24fa7a0a0..87864fc7e 100644 --- a/old/bin/old-handler.pl +++ b/old/bin/old-handler.pl @@ -187,14 +187,16 @@ $form->{dbh}->disconnect() if defined $form->{dbh}; sub _error { my ($form, $msg, $status) = @_; $msg = "? _error" if !defined $msg; + my $html_msg = escape_html($msg); + my $html_dbversion = escape_html($form->{dbversion}); + my $html_company = escape_html($form->{company}); $status = 500 if ! defined $status;
print qq|Status: $status ISE Content-Type: text/html; charset=utf-8 - <html> -<body><h2 class="error">Error!</h2> <p><b>$msg</b></p> -<p>dbversion: $form->{dbversion}, company: $form->{company}</p> +<body><h2 class="error">Error!</h2> <p><b>$html_msg</b></p> +<p>dbversion: $html_dbversion, company: $html_company</p> </body> </html> |; ]]]