Vulnerabilities > CVE-2017-2808 - Use After Free vulnerability in Ledger-Cli Ledger 3.1.1

047910
CVSS 7.8 - HIGH
Attack vector
LOCAL
Attack complexity
LOW
Privileges required
NONE
Confidentiality impact
HIGH
Integrity impact
HIGH
Availability impact
HIGH
local
low complexity
ledger-cli
CWE-416
nessus

Summary

An exploitable use-after-free vulnerability exists in the account parsing component of the Ledger-CLI 3.1.1. A specially crafted ledger file can cause a use-after-free vulnerability resulting in arbitrary code execution. An attacker can convince a user to load a journal file to trigger this vulnerability.

Vulnerable Configurations

Part Description Count
Application
Ledger-Cli
1

Common Weakness Enumeration (CWE)

Nessus

  • NASL familyFreeBSD Local Security Checks
    NASL idFREEBSD_PKG_D843A9847F22484FBA81483DDBE30DC3.NASL
    descriptionTalos reports : An exploitable buffer overflow vulnerability exists in the tag parsing functionality of Ledger-CLI 3.1.1. A specially crafted journal file can cause an integer underflow resulting in code execution. An attacker can construct a malicious journal file to trigger this vulnerability. An exploitable use-after-free vulnerability exists in the account parsing component of the Ledger-CLI 3.1.1. A specially crafted ledger file can cause a use-after-free vulnerability resulting in arbitrary code execution. An attacker can convince a user to load a journal file to trigger this vulnerability.
    last seen2020-06-01
    modified2020-06-02
    plugin id103482
    published2017-09-27
    reporterThis script is Copyright (C) 2017-2018 and is owned by Tenable, Inc. or an Affiliate thereof.
    sourcehttps://www.tenable.com/plugins/nessus/103482
    titleFreeBSD : ledger -- multiple vulnerabilities (d843a984-7f22-484f-ba81-483ddbe30dc3)
    code
    #
    # (C) Tenable Network Security, Inc.
    #
    # The descriptive text and package checks in this plugin were  
    # extracted from the FreeBSD VuXML database :
    #
    # Copyright 2003-2018 Jacques Vidrine and contributors
    #
    # Redistribution and use in source (VuXML) and 'compiled' forms (SGML,
    # HTML, PDF, PostScript, RTF and so forth) with or without modification,
    # are permitted provided that the following conditions are met:
    # 1. Redistributions of source code (VuXML) must retain the above
    #    copyright notice, this list of conditions and the following
    #    disclaimer as the first lines of this file unmodified.
    # 2. Redistributions in compiled form (transformed to other DTDs,
    #    published online in any format, converted to PDF, PostScript,
    #    RTF and other formats) must reproduce the above copyright
    #    notice, this list of conditions and the following disclaimer
    #    in the documentation and/or other materials provided with the
    #    distribution.
    # 
    # THIS DOCUMENTATION IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS"
    # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
    # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
    # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS
    # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
    # OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
    # OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
    # BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
    # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
    # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS DOCUMENTATION,
    # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    #
    
    include("compat.inc");
    
    if (description)
    {
      script_id(103482);
      script_version("3.4");
      script_cvs_date("Date: 2018/11/23 12:49:58");
    
      script_cve_id("CVE-2017-2807", "CVE-2017-2808");
    
      script_name(english:"FreeBSD : ledger -- multiple vulnerabilities (d843a984-7f22-484f-ba81-483ddbe30dc3)");
      script_summary(english:"Checks for updated package in pkg_info output");
    
      script_set_attribute(
        attribute:"synopsis", 
        value:"The remote FreeBSD host is missing a security-related update."
      );
      script_set_attribute(
        attribute:"description", 
        value:
    "Talos reports :
    
    An exploitable buffer overflow vulnerability exists in the tag parsing
    functionality of Ledger-CLI 3.1.1. A specially crafted journal file
    can cause an integer underflow resulting in code execution. An
    attacker can construct a malicious journal file to trigger this
    vulnerability.
    
    An exploitable use-after-free vulnerability exists in the account
    parsing component of the Ledger-CLI 3.1.1. A specially crafted ledger
    file can cause a use-after-free vulnerability resulting in arbitrary
    code execution. An attacker can convince a user to load a journal file
    to trigger this vulnerability."
      );
      # http://www.securityfocus.com/bid/100543
      script_set_attribute(
        attribute:"see_also",
        value:"https://www.securityfocus.com/bid/100543"
      );
      # https://www.talosintelligence.com/vulnerability_reports/TALOS-2017-0303
      script_set_attribute(
        attribute:"see_also",
        value:"http://www.nessus.org/u?0f44e2cc"
      );
      # http://www.securityfocus.com/bid/100546
      script_set_attribute(
        attribute:"see_also",
        value:"https://www.securityfocus.com/bid/100546"
      );
      # https://www.talosintelligence.com/vulnerability_reports/TALOS-2017-0304
      script_set_attribute(
        attribute:"see_also",
        value:"http://www.nessus.org/u?c52caa81"
      );
      # https://vuxml.freebsd.org/freebsd/d843a984-7f22-484f-ba81-483ddbe30dc3.html
      script_set_attribute(
        attribute:"see_also",
        value:"http://www.nessus.org/u?4d37cbae"
      );
      script_set_attribute(attribute:"solution", value:"Update the affected package.");
      script_set_cvss_base_vector("CVSS2#AV:N/AC:M/Au:N/C:P/I:P/A:P");
      script_set_cvss3_base_vector("CVSS:3.0/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H");
    
      script_set_attribute(attribute:"plugin_type", value:"local");
      script_set_attribute(attribute:"cpe", value:"p-cpe:/a:freebsd:freebsd:ledger");
      script_set_attribute(attribute:"cpe", value:"cpe:/o:freebsd:freebsd");
    
      script_set_attribute(attribute:"vuln_publication_date", value:"2017/09/05");
      script_set_attribute(attribute:"patch_publication_date", value:"2017/09/26");
      script_set_attribute(attribute:"plugin_publication_date", value:"2017/09/27");
      script_end_attributes();
    
      script_category(ACT_GATHER_INFO);
      script_copyright(english:"This script is Copyright (C) 2017-2018 and is owned by Tenable, Inc. or an Affiliate thereof.");
      script_family(english:"FreeBSD Local Security Checks");
    
      script_dependencies("ssh_get_info.nasl");
      script_require_keys("Host/local_checks_enabled", "Host/FreeBSD/release", "Host/FreeBSD/pkg_info");
    
      exit(0);
    }
    
    
    include("audit.inc");
    include("freebsd_package.inc");
    
    
    if (!get_kb_item("Host/local_checks_enabled")) audit(AUDIT_LOCAL_CHECKS_NOT_ENABLED);
    if (!get_kb_item("Host/FreeBSD/release")) audit(AUDIT_OS_NOT, "FreeBSD");
    if (!get_kb_item("Host/FreeBSD/pkg_info")) audit(AUDIT_PACKAGE_LIST_MISSING);
    
    
    flag = 0;
    
    if (pkg_test(save_report:TRUE, pkg:"ledger<=3.1.1")) flag++;
    
    if (flag)
    {
      if (report_verbosity > 0) security_warning(port:0, extra:pkg_report_get());
      else security_warning(0);
      exit(0);
    }
    else audit(AUDIT_HOST_NOT, "affected");
    
  • NASL familySuSE Local Security Checks
    NASL idOPENSUSE-2019-1779.NASL
    descriptionThis update for ledger fixes the following issues : ledger was updated to 3.1.3 : + Properly reject postings with a comment right after the flag (bug #1753) + Make sorting order of lot information deterministic (bug #1747) + Fix bug in tag value parsing (bug #1702) + Remove the org command, which was always a hack to begin with (bug #1706) + Provide Docker information in README + Various small documentation improvements This also includes the update to 3.1.2 : + Increase maximum length for regex from 255 to 4095 (bug #981) + Initialize periods from from/since clause rather than earliest transaction date (bug #1159) + Check balance assertions against the amount after the posting (bug #1147) + Allow balance assertions with multiple posts to same account (bug #1187) + Fix period duration of
    last seen2020-06-01
    modified2020-06-02
    plugin id126909
    published2019-07-22
    reporterThis script is Copyright (C) 2019-2020 and is owned by Tenable, Inc. or an Affiliate thereof.
    sourcehttps://www.tenable.com/plugins/nessus/126909
    titleopenSUSE Security Update : ledger (openSUSE-2019-1779)
    code
    #
    # (C) Tenable Network Security, Inc.
    #
    # The descriptive text and package checks in this plugin were
    # extracted from openSUSE Security Update openSUSE-2019-1779.
    #
    # The text description of this plugin is (C) SUSE LLC.
    #
    
    include("compat.inc");
    
    if (description)
    {
      script_id(126909);
      script_version("1.2");
      script_cvs_date("Date: 2020/01/08");
    
      script_cve_id("CVE-2017-12481", "CVE-2017-12482", "CVE-2017-2807", "CVE-2017-2808");
    
      script_name(english:"openSUSE Security Update : ledger (openSUSE-2019-1779)");
      script_summary(english:"Check for the openSUSE-2019-1779 patch");
    
      script_set_attribute(
        attribute:"synopsis", 
        value:"The remote openSUSE host is missing a security update."
      );
      script_set_attribute(
        attribute:"description", 
        value:
    "This update for ledger fixes the following issues :
    
    ledger was updated to 3.1.3 :
    
      + Properly reject postings with a comment right after the
        flag (bug #1753)
    
      + Make sorting order of lot information deterministic (bug
        #1747)
    
      + Fix bug in tag value parsing (bug #1702)
    
      + Remove the org command, which was always a hack to begin
        with (bug #1706)
    
      + Provide Docker information in README
    
      + Various small documentation improvements 
    
    This also includes the update to 3.1.2 :
    
      + Increase maximum length for regex from 255 to 4095 (bug
        #981)
    
      + Initialize periods from from/since clause rather than
        earliest transaction date (bug #1159)
    
      + Check balance assertions against the amount after the
        posting (bug #1147)
    
      + Allow balance assertions with multiple posts to same
        account (bug #1187)
    
      + Fix period duration of 'every X days' and similar
        statements (bug #370)
    
      + Make option --force-color not require --color anymore
        (bug #1109)
    
      + Add quoted_rfc4180 to allow CVS output with RFC 4180
        compliant quoting.
    
      + Add support for --prepend-format in accounts command
    
      + Fix handling of edge cases in trim function (bug #520)
    
      + Fix auto xact posts not getting applied to account total
        during journal parse (bug #552)
    
      + Transfer null_post flags to generated postings
    
      + Fix segfault when using --market with --group-by
    
      + Use amount_width variable for budget report
    
      + Keep pending items in budgets until the last day they
        apply
    
      + Fix bug where .total used in value expressions breaks
        totals
    
      + Make automated transactions work with assertions (bug
        #1127)
    
      + Improve parsing of date tokens (bug #1626)
    
      + Don't attempt to invert a value if it's already zero
        (bug #1703)
    
      + Do not parse user-specified init-file twice
    
      + Fix parsing issue of effective dates (bug #1722,
        TALOS-2017-0303, CVE-2017-2807)
    
      + Fix use-after-free issue with deferred postings (bug
        #1723, TALOS-2017-0304, CVE-2017-2808)
    
      + Fix possible stack overflow in option parsing routine
        (bug #1222, CVE-2017-12481)
    
      + Fix possible stack overflow in date parsing routine (bug
        #1224, CVE-2017-12482)
    
      + Fix use-after-free when using --gain (bug #541)
    
      + Python: Removed double quotes from Unicode values.
    
      + Python: Ensure that parse errors produce useful
        RuntimeErrors
    
      + Python: Expose journal expand_aliases
    
      + Python: Expose journal_t::register_account
    
      + Improve bash completion
    
      + Emacs Lisp files have been moved to
        https://github.com/ledger/ledger-mode
    
      + Various documentation improvements"
      );
      script_set_attribute(
        attribute:"see_also",
        value:"https://bugzilla.opensuse.org/show_bug.cgi?id=1052478"
      );
      script_set_attribute(
        attribute:"see_also",
        value:"https://bugzilla.opensuse.org/show_bug.cgi?id=1052484"
      );
      script_set_attribute(
        attribute:"see_also",
        value:"https://bugzilla.opensuse.org/show_bug.cgi?id=1105084"
      );
      script_set_attribute(
        attribute:"see_also",
        value:"https://github.com/ledger/ledger-mode"
      );
      script_set_attribute(
        attribute:"solution", 
        value:"Update the affected ledger packages."
      );
      script_set_cvss_base_vector("CVSS2#AV:N/AC:M/Au:N/C:P/I:P/A:P");
      script_set_cvss_temporal_vector("CVSS2#E:U/RL:OF/RC:C");
      script_set_cvss3_base_vector("CVSS:3.0/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H");
      script_set_cvss3_temporal_vector("CVSS:3.0/E:U/RL:O/RC:C");
      script_set_attribute(attribute:"exploitability_ease", value:"No known exploits are available");
    
      script_set_attribute(attribute:"plugin_type", value:"local");
      script_set_attribute(attribute:"cpe", value:"p-cpe:/a:novell:opensuse:ledger");
      script_set_attribute(attribute:"cpe", value:"p-cpe:/a:novell:opensuse:ledger-debuginfo");
      script_set_attribute(attribute:"cpe", value:"p-cpe:/a:novell:opensuse:ledger-debugsource");
      script_set_attribute(attribute:"cpe", value:"cpe:/o:novell:opensuse:15.0");
      script_set_attribute(attribute:"cpe", value:"cpe:/o:novell:opensuse:15.1");
    
      script_set_attribute(attribute:"vuln_publication_date", value:"2017/08/04");
      script_set_attribute(attribute:"patch_publication_date", value:"2019/07/21");
      script_set_attribute(attribute:"plugin_publication_date", value:"2019/07/22");
      script_set_attribute(attribute:"generated_plugin", value:"current");
      script_end_attributes();
    
      script_category(ACT_GATHER_INFO);
      script_copyright(english:"This script is Copyright (C) 2019-2020 and is owned by Tenable, Inc. or an Affiliate thereof.");
      script_family(english:"SuSE Local Security Checks");
    
      script_dependencies("ssh_get_info.nasl");
      script_require_keys("Host/local_checks_enabled", "Host/SuSE/release", "Host/SuSE/rpm-list", "Host/cpu");
    
      exit(0);
    }
    
    
    include("audit.inc");
    include("global_settings.inc");
    include("rpm.inc");
    
    if (!get_kb_item("Host/local_checks_enabled")) audit(AUDIT_LOCAL_CHECKS_NOT_ENABLED);
    release = get_kb_item("Host/SuSE/release");
    if (isnull(release) || release =~ "^(SLED|SLES)") audit(AUDIT_OS_NOT, "openSUSE");
    if (release !~ "^(SUSE15\.0|SUSE15\.1)$") audit(AUDIT_OS_RELEASE_NOT, "openSUSE", "15.0 / 15.1", release);
    if (!get_kb_item("Host/SuSE/rpm-list")) audit(AUDIT_PACKAGE_LIST_MISSING);
    
    ourarch = get_kb_item("Host/cpu");
    if (!ourarch) audit(AUDIT_UNKNOWN_ARCH);
    if (ourarch !~ "^(x86_64)$") audit(AUDIT_ARCH_NOT, "x86_64", ourarch);
    
    flag = 0;
    
    if ( rpm_check(release:"SUSE15.0", reference:"ledger-3.1.3-lp150.2.3.1") ) flag++;
    if ( rpm_check(release:"SUSE15.0", reference:"ledger-debuginfo-3.1.3-lp150.2.3.1") ) flag++;
    if ( rpm_check(release:"SUSE15.0", reference:"ledger-debugsource-3.1.3-lp150.2.3.1") ) flag++;
    if ( rpm_check(release:"SUSE15.1", reference:"ledger-3.1.3-lp151.3.3.1") ) flag++;
    if ( rpm_check(release:"SUSE15.1", reference:"ledger-debuginfo-3.1.3-lp151.3.3.1") ) flag++;
    if ( rpm_check(release:"SUSE15.1", reference:"ledger-debugsource-3.1.3-lp151.3.3.1") ) flag++;
    
    if (flag)
    {
      if (report_verbosity > 0) security_warning(port:0, extra:rpm_report_get());
      else security_warning(0);
      exit(0);
    }
    else
    {
      tested = pkg_tests_get();
      if (tested) audit(AUDIT_PACKAGE_NOT_AFFECTED, tested);
      else audit(AUDIT_PACKAGE_NOT_INSTALLED, "ledger / ledger-debuginfo / ledger-debugsource");
    }
    
  • NASL familyGentoo Local Security Checks
    NASL idGENTOO_GLSA-202004-05.NASL
    descriptionThe remote host is affected by the vulnerability described in GLSA-202004-05 (ledger: Multiple vulnerabilities) Multiple vulnerabilities have been discovered in ledger. Please review the CVE identifiers referenced below for details. Impact : A remote attacker could entice a user to process a specially crafted file using ledger, possibly resulting in execution of arbitrary code with the privileges of the process or a Denial of Service condition. Workaround : There is no known workaround at this time.
    last seen2020-04-07
    modified2020-04-02
    plugin id135116
    published2020-04-02
    reporterThis script is Copyright (C) 2020 and is owned by Tenable, Inc. or an Affiliate thereof.
    sourcehttps://www.tenable.com/plugins/nessus/135116
    titleGLSA-202004-05 : ledger: Multiple vulnerabilities
    code
    #
    # (C) Tenable Network Security, Inc.
    #
    # The descriptive text and package checks in this plugin were
    # extracted from Gentoo Linux Security Advisory GLSA 202004-05.
    #
    # The advisory text is Copyright (C) 2001-2020 Gentoo Foundation, Inc.
    # and licensed under the Creative Commons - Attribution / Share Alike 
    # license. See http://creativecommons.org/licenses/by-sa/3.0/
    #
    
    include("compat.inc");
    
    if (description)
    {
      script_id(135116);
      script_version("1.2");
      script_set_attribute(attribute:"plugin_modification_date", value:"2020/04/06");
    
      script_cve_id("CVE-2017-12481", "CVE-2017-12482", "CVE-2017-2807", "CVE-2017-2808");
      script_xref(name:"GLSA", value:"202004-05");
    
      script_name(english:"GLSA-202004-05 : ledger: Multiple vulnerabilities");
      script_summary(english:"Checks for updated package(s) in /var/db/pkg");
    
      script_set_attribute(
        attribute:"synopsis", 
        value:
    "The remote Gentoo host is missing one or more security-related
    patches."
      );
      script_set_attribute(
        attribute:"description", 
        value:
    "The remote host is affected by the vulnerability described in GLSA-202004-05
    (ledger: Multiple vulnerabilities)
    
        Multiple vulnerabilities have been discovered in ledger. Please review
          the CVE identifiers referenced below for details.
      
    Impact :
    
        A remote attacker could entice a user to process a specially crafted
          file using ledger, possibly resulting in execution of arbitrary code with
          the privileges of the process or a Denial of Service condition.
      
    Workaround :
    
        There is no known workaround at this time."
      );
      script_set_attribute(
        attribute:"see_also",
        value:"https://security.gentoo.org/glsa/202004-05"
      );
      script_set_attribute(
        attribute:"solution", 
        value:
    "All ledger users should upgrade to the latest version:
          # emerge --sync
          # emerge --ask --oneshot --verbose '>=app-office/ledger-3.1.2'"
      );
      script_set_cvss_base_vector("CVSS2#AV:N/AC:M/Au:N/C:P/I:P/A:P");
      script_set_cvss_temporal_vector("CVSS2#E:U/RL:OF/RC:C");
      script_set_cvss3_base_vector("CVSS:3.0/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H");
      script_set_cvss3_temporal_vector("CVSS:3.0/E:U/RL:O/RC:C");
      script_set_attribute(attribute:"exploitability_ease", value:"No known exploits are available");
    
      script_set_attribute(attribute:"plugin_type", value:"local");
      script_set_attribute(attribute:"cpe", value:"p-cpe:/a:gentoo:linux:ledger");
      script_set_attribute(attribute:"cpe", value:"cpe:/o:gentoo:linux");
    
      script_set_attribute(attribute:"vuln_publication_date", value:"2017/08/04");
      script_set_attribute(attribute:"patch_publication_date", value:"2020/04/01");
      script_set_attribute(attribute:"plugin_publication_date", value:"2020/04/02");
      script_set_attribute(attribute:"generated_plugin", value:"current");
      script_end_attributes();
    
      script_category(ACT_GATHER_INFO);
      script_copyright(english:"This script is Copyright (C) 2020 and is owned by Tenable, Inc. or an Affiliate thereof.");
      script_family(english:"Gentoo Local Security Checks");
    
      script_dependencies("ssh_get_info.nasl");
      script_require_keys("Host/local_checks_enabled", "Host/Gentoo/release", "Host/Gentoo/qpkg-list");
    
      exit(0);
    }
    
    
    include("audit.inc");
    include("global_settings.inc");
    include("qpkg.inc");
    
    if (!get_kb_item("Host/local_checks_enabled")) audit(AUDIT_LOCAL_CHECKS_NOT_ENABLED);
    if (!get_kb_item("Host/Gentoo/release")) audit(AUDIT_OS_NOT, "Gentoo");
    if (!get_kb_item("Host/Gentoo/qpkg-list")) audit(AUDIT_PACKAGE_LIST_MISSING);
    
    
    flag = 0;
    
    if (qpkg_check(package:"app-office/ledger", unaffected:make_list("ge 3.1.2"), vulnerable:make_list("lt 3.1.2"))) flag++;
    
    if (flag)
    {
      if (report_verbosity > 0) security_warning(port:0, extra:qpkg_report_get());
      else security_warning(0);
      exit(0);
    }
    else
    {
      tested = qpkg_tests_get();
      if (tested) audit(AUDIT_PACKAGE_NOT_AFFECTED, tested);
      else audit(AUDIT_PACKAGE_NOT_INSTALLED, "ledger");
    }
    

Seebug

bulletinFamilyexploit
description### Summary An exploitable use-after-free vulnerability exists in the account parsing component of the Ledger-CLI 3.1.1. A specially crafted ledger file can cause a use-after-free vulnerability resulting in arbitrary code execution. An attacker can convince a user to load a journal file to trigger this vulnerability. ### Tested Versions Ledger HEAD Ledger 3.1.1 ### Product URLs http://ledger-cli.org https://github.com/ledger/ledger.git https://github.com/ledger/ledger/tree/v3.1.1 ### CVSSv3 Score 7.5 - CVSS:3.0/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H ### CWE CWE-416: Use After Free ### Details Ledger-cli is a plain-text, double-entry accounting system that is useable from the command line. It is based around a plain-text format known as a journal file that contains each transaction for each account. When parsing a malformed journal file, the application will misuse the std::unique_ptr type when exchanging objects between different parts of the parser. Due to this, when these pointers go out of scope they will be released and destroyed. However, due to the pointers still being retained by the application a use-after-free vulnerability will occur when the application attempts to process a user's commands. When first parsing a journal file, the application will execute the following code. This code will instantiate an instance_t, and then call its `.parse()` method [1]. The `.parse()` method will simply enter a loop that processes each line of the journal file using the `read_next_directive` method [2]. ``` src/textual.cc:1987 std::size_t journal_t::read_textual(parse_context_stack_t& context_stack) { TRACE_START(parsing_total, 1, "Total time spent parsing text:"); { instance_t instance(context_stack, context_stack.get_current(), NULL, checking_style == journal_t::CHECK_PERMISSIVE); instance.apply_stack.push_front (application_t("account", context_stack.get_current().master)); instance.parse(); // \ [1] } TRACE_STOP(parsing_total, 1); // Apply any deferred postings at this time master->apply_deferred_posts(); ... } \ src/textual.cc:236 void instance_t::parse(() { ... while (in.good() && ! in.eof()) { try { read_next_directive(error_flag); // [2] } catch (const std::exception& err) { ``` The `read_next_directive` method will iterate through each line of the journal file whilst looking at the first character to determine which command to process. If the first character is numeric, then the application will assume that it's an account directive and will call the `xact_directive` method to handle it [1]. ``` src/textual.cc:334 void instance_t::read_next_directive(bool& error_flag) char * line; std::streamsize len = read_line(line); if (len == 0 || line == NULL) return; if (! std::isspace(line[0])) error_flag = false; switch (line[0]) { ... case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': xact_directive(line, len); // [1] break; ... ``` Inside the `xact_directive` method, the application will call the `parse_xact` [1] method which will return a pointer. This pointer will then be assigned to a `unique_ptr<xact_t>` [2]. Due to it being incorrectly initialized from a pointer, there are no references to it. When the variable goes out of scope at [3], it will then be freed. ``` src/textual.cc:694 void instance_t::xact_directive(char * line, std::streamsize len) TRACE_START(xacts, 1, "Time spent handling transactions:"); if (xact_t * xact = parse_xact(line, len, top_account())) { // [1] unique_ptr<xact_t> manager(xact); // [2] if (context.journal->add_xact(xact)) { manager.release(); // it's owned by the journal now context.count++; } // It's perfectly valid for the journal to reject the xact, which it // will do if the xact has no substantive effect (for example, a // checking xact, all of whose postings have null amounts). } else { // [3] throw parse_error(_("Failed to parse transaction")); TRACE_STOP(xacts, 1); ``` The `parse_xact method`, is responsible for allocating the `xact_t` object [1]. Near the end of this function, the application will call the `parse_post` method [2]. This method will allocate a post_t object, and then link the xact_t object to the `post_t`. Afterwards, the returned `post_t` will then be linked back to the `xact_t` object [3]. ``` src/textual.cc:1776 xact_t * instance_t::parse_xact(char * line, std::streamsize len, account_t * account) TRACE_START(xact_text, 1, "Time spent parsing transaction text:"); unique_ptr<xact_t> xact(new xact_t); // [1] xact->pos = position_t(); xact->pos->pathname = context.pathname; xact->pos->beg_pos = context.line_beg_pos; xact->pos->beg_line = context.linenum; xact->pos->sequence = context.sequence++; bool reveal_context = true; ... if (post_t * post = parse_post(p, len - (p - line), account, xact.get())) { // [2] reveal_context = true; xact->add_post(post); // [3] last_post = post; } reveal_context = true; } } ... return xact.release(); ``` Inside the `parse_post` method, the application will allocate a `post_t` via it's constructor [1], and then begin to parse any extra flags that were specified in the post. In order to reach the most vulnerable path which leads directly to code eecution, the `POST_DEFERRED` flag must be specified. This requires that there be an entry that begins and ends with the `< and >` characters [2]. ``` src/textual.cc:1404 post_t * instance_t::parse_post(char * line, std::streamsize len, account_t * account, xact_t * xact, bool defer_expr) TRACE_START(post_details, 1, "Time spent parsing postings:"); unique_ptr<post_t> post(new post_t); // [1] post->xact = xact; // this could be NULL post->pos = position_t(); post->pos->pathname = context.pathname; post->pos->beg_pos = context.line_beg_pos; post->pos->beg_line = context.linenum; post->pos->sequence = context.sequence++; ... else if (*p == '<' && *(e - 1) == '>') { post->add_flags(POST_DEFERRED); // [2] DEBUG("textual.parse", "line " << context.linenum << ": " << "Parsed a deferred account name"); p++; e--; } ... return post.release(); ... ``` Once the file is done parsing, the application will return back to the `journal_t::read_textual` method. At [1], the application will then proceed to apply any transactional information for the deferred posts in the `master` account that have been parsed by the `instance_t` parser. ``` src/textual.cc:1987 std::size_t journal_t::read_textual(parse_context_stack_t& context_stack) TRACE_START(parsing_total, 1, "Total time spent parsing text:"); { instance_t instance(context_stack, context_stack.get_current(), NULL, checking_style == journal_t::CHECK_PERMISSIVE); instance.apply_stack.push_front (application_t("account", context_stack.get_current().master)); instance.parse(); } TRACE_STOP(parsing_total, 1); // Apply any deferred postings at this time master->apply_deferred_posts(); // [1] ``` This is done by the following code. At this point, the post object has already been freed due to it being out of scope due to lack of references. At [1], since the pointer was still assigned to the object, the application will iterate through all the deferred posts within the account. However, due to the object being released, the virtual method dereference at [2] will dereference memory that has gone out of scope. ``` src/account.cc:157 void account_t::apply_deferred_posts() if (deferred_posts) { foreach (deferred_posts_map_t::value_type& pair, *deferred_posts) { // [1] foreach (post_t * post, pair.second) post->account->add_post(post); // [2] } deferred_posts = none; } // Also apply in child accounts foreach (const accounts_map::value_type& pair, accounts) pair.second->apply_deferred_posts(); ``` ### Crash Information ``` $ ledger -f poc.journal register ================================================================= ==20621==ERROR: AddressSanitizer: heap-use-after-free on address 0x6150000012d0 at pc 0x7f50d6b0767b bp 0x7ffe5b6bca70 sp 0x7ffe5b6bca68 READ of size 8 at 0x6150000012d0 thread T0 #0 0x7f50d6b0767a in ledger::account_t::apply_deferred_posts() /root/ledger/src/account.cc:162:15 #1 0x7f50d6b07638 in ledger::account_t::apply_deferred_posts() /root/ledger/src/account.cc:169:18 #2 0x7f50d6a776ce in ledger::journal_t::read_textual(ledger::parse_context_stack_t&) /root/ledger/src/textual.cc:2000:11 #3 0x7f50d6af9d3f in ledger::journal_t::read(ledger::parse_context_stack_t&) /root/ledger/src/journal.cc:505:13 #4 0x7f50d6a1056b in ledger::session_t::read_data(std::string const&) /root/ledger/src/session.cc:171:30 #5 0x7f50d6a150bc in ledger::session_t::read_journal_files() /root/ledger/src/session.cc:203:5 #6 0x567893 in ledger::global_scope_t::execute_command(std::list<std::string, std::allocator<std::string> >, bool) /root/ledger/src/global.cc:228:17 #7 0x56c3e4 in ledger::global_scope_t::execute_command_wrapper(std::list<std::string, std::allocator<std::string> >, bool) /root/ledger/src/global.cc:273:5 #8 0x53c996 in main /root/ledger/src/main.cc:121:30 #9 0x7f50d3c297ec in __libc_start_main /build/eglibc-wIuxyX/eglibc-2.15/csu/libc-start.c:226 #10 0x43fa20 in _start (/root/ledger/build/ledger+0x43fa20) 0x6150000012d0 is located 208 bytes inside of 472-byte region [0x615000001200,0x6150000013d8) freed by thread T0 here: #0 0x538410 in operator delete(void*) (/root/ledger/build/ledger+0x538410) #1 0x7f50d6b2137d in void boost::checked_delete<ledger::post_t>(ledger::post_t*) /usr/include/boost/checked_delete.hpp:34:5 #2 0x7f50d6b2137d in ledger::xact_base_t::~xact_base_t() /root/ledger/src/xact.cc:62 #3 0x7f50d6b413fd in ledger::xact_t::~xact_t() /root/ledger/src/xact.h:113:3 #4 0x7f50d6b413fd in ledger::xact_t::~xact_t() /root/ledger/src/xact.h:111 #5 0x7f50d6a8447b in std::default_delete<ledger::xact_t>::operator()(ledger::xact_t*) const /usr/bin/../lib/gcc/x86_64-linux- gnu/4.9/../../../../include/c++/4.9/bits/unique_ptr.h:76:2 #6 0x7f50d6a8447b in std::unique_ptr<ledger::xact_t, std::default_delete<ledger::xact_t> >::~unique_ptr() /usr/bin/../lib/gcc/x86_64-linux-gnu/4.9/../../../../include/c++/4.9/bits/unique_ptr.h:236 #7 0x7f50d6a8447b in ledger::(anonymous namespace)::instance_t::xact_directive(char*, long) /root/ledger/src/textual.cc:708 #8 0x7f50d6a8447b in ledger::(anonymous namespace)::instance_t::read_next_directive(bool&) /root/ledger/src/textual.cc:375 #9 0x7f50d6a79322 in ledger::(anonymous namespace)::instance_t::parse() /root/ledger/src/textual.cc:252:7 #10 0x7f50d6a77594 in ledger::journal_t::read_textual(ledger::parse_context_stack_t&) /root/ledger/src/textual.cc:1995:14 #11 0x7f50d6af9d3f in ledger::journal_t::read(ledger::parse_context_stack_t&) /root/ledger/src/journal.cc:505:13 #12 0x7f50d6a1056b in ledger::session_t::read_data(std::string const&) /root/ledger/src/session.cc:171:30 #13 0x7f50d6a150bc in ledger::session_t::read_journal_files() /root/ledger/src/session.cc:203:5 #14 0x567893 in ledger::global_scope_t::execute_command(std::list<std::string, std::allocator<std::string> >, bool) /root/ledger/src/global.cc:228:17 #15 0x56c3e4 in ledger::global_scope_t::execute_command_wrapper(std::list<std::string, std::allocator<std::string> >, bool) /root/ledger/src/global.cc:273:5 #16 0x53c996 in main /root/ledger/src/main.cc:121:30 #17 0x7f50d3c297ec in __libc_start_main /build/eglibc-wIuxyX/eglibc-2.15/csu/libc-start.c:226 previously allocated by thread T0 here: #0 0x5376d0 in operator new(unsigned long) (/root/ledger/build/ledger+0x5376d0) #1 0x7f50d6ab8e85 in ledger::(anonymous namespace)::instance_t::parse_post(char*, long, ledger::account_t*, ledger::xact_t*, bool) /root/ledger/src/textual.cc:1412:27 #2 0x7f50d6a82791 in ledger::(anonymous namespace)::instance_t::parse_xact(char*, long, ledger::account_t*) /root/ledger/src/textual.cc:1932:11 #3 0x7f50d6a82791 in ledger::(anonymous namespace)::instance_t::xact_directive(char*, long) /root/ledger/src/textual.cc:698 #4 0x7f50d6a82791 in ledger::(anonymous namespace)::instance_t::read_next_directive(bool&) /root/ledger/src/textual.cc:375 #5 0x7f50d6a79322 in ledger::(anonymous namespace)::instance_t::parse() /root/ledger/src/textual.cc:252:7 #6 0x7f50d6a77594 in ledger::journal_t::read_textual(ledger::parse_context_stack_t&) /root/ledger/src/textual.cc:1995:14 #7 0x7f50d6af9d3f in ledger::journal_t::read(ledger::parse_context_stack_t&) /root/ledger/src/journal.cc:505:13 #8 0x7f50d6a1056b in ledger::session_t::read_data(std::string const&) /root/ledger/src/session.cc:171:30 #9 0x7f50d6a150bc in ledger::session_t::read_journal_files() /root/ledger/src/session.cc:203:5 #10 0x567893 in ledger::global_scope_t::execute_command(std::list<std::string, std::allocator<std::string> >, bool) /root/ledger/src/global.cc:228:17 #11 0x56c3e4 in ledger::global_scope_t::execute_command_wrapper(std::list<std::string, std::allocator<std::string> >, bool) /root/ledger/src/global.cc:273:5 #12 0x53c996 in main /root/ledger/src/main.cc:121:30 #13 0x7f50d3c297ec in __libc_start_main /build/eglibc-wIuxyX/eglibc-2.15/csu/libc-start.c:226 SUMMARY: AddressSanitizer: heap-use-after-free /root/ledger/src/account.cc:162:15 in ledger::account_t::apply_deferred_posts() Shadow bytes around the buggy address: 0x0c2a7fff8200: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd 0x0c2a7fff8210: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd 0x0c2a7fff8220: fd fd fd fd fd fd fd fd fd fd fd fa fa fa fa fa 0x0c2a7fff8230: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c2a7fff8240: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd =>0x0c2a7fff8250: fd fd fd fd fd fd fd fd fd fd[fd]fd fd fd fd fd 0x0c2a7fff8260: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd 0x0c2a7fff8270: fd fd fd fd fd fd fd fd fd fd fd fa fa fa fa fa 0x0c2a7fff8280: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c2a7fff8290: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c2a7fff82a0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa Shadow byte legend (one shadow byte represents 8 application bytes): Addressable: 00 Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: fa Freed heap region: fd Stack left redzone: f1 Stack mid redzone: f2 Stack right redzone: f3 Stack after return: f5 Stack use after scope: f8 Global redzone: f9 Global init order: f6 Poisoned by user: f7 Container overflow: fc Array cookie: ac Intra object redzone: bb ASan internal: fe Left alloca redzone: ca Right alloca redzone: cb ==20621==ABORTING ``` ### Exploit Proof-of-Concept Simply run the ledger binary with the provided proof-of-concept as an argument followed by the command type. Both `xml` and `register` will trigger the vulnerability. ``` $ ledger -f poc.sample [xml|register] ``` The proof-of-concept simply needs to have an account entry within it that includes a deferred posting. This means that a line must exist that begins with a number to specify an account directive. Then before the next account directive, there must be something within `'<' and '>'`symbols. This will enter the path that will dereference a function pointer. ### Timeline * 2017-04-07 - Vendor Disclosure * 2017-08-30 - Public Release ### CREDIT * Discovered by Cory Duplantis and another member of Cisco Talos.
idSSV:96444
last seen2017-11-19
modified2017-09-12
published2017-09-12
reporterRoot
titleLedger CLI Account Directive Use-After-Free Vulnerability(CVE-2017-2808)

Talos

idTALOS-2017-0304
last seen2019-05-29
published2017-08-30
reporterTalos Intelligence
sourcehttp://www.talosintelligence.com/vulnerability_reports/TALOS-2017-0304
titleLedger CLI Account Directive Use-After-Free Vulnerability