Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Defect to Doctrine: Security Bug Case Studies

Defect to Doctrine: Security Bug Case Studies

I am not a security research or bug bounty hunter. Just a humble programmer who has found and fixed some security bugs over the years. I admire exploit authors who take advantage of programmers' mistakes to penetrate, pivot and profit. But this is not that talk.

As an industry, we can (and must) improve the security of the the systems we write by learning from our mistakes. Every bug tells a story. Every story has a moral, if you care to look for it.

In this talk I will describe four different vulnerabilities in programs I worked on, including FreeIPA, Dogtag PKI and Firefox. I will explain what the bug was, it's impact, how it was discovered and how I resolved it. From each case study I will develop one or two important principles for secure programming.

This presentation will be most useful for programmers, engineering managers, and security folk who want an engineer's perspective on how issues arise and how to avoid them.

Fraser Tweedale

April 06, 2019
Tweet

More Decks by Fraser Tweedale

Other Decks in Programming

Transcript

  1. From Defect to Doctrine
    Security bug case studies
    Fraser Tweedale
    @hackuador
    April 4, 2019

    View Slide

  2. Dogtag PKI

    View Slide

  3. Access control - definition
    certServer.ca.authorities
    :create,modify
    :allow (list,read) user="anybody";
    allow (create,modify,delete) group="Administrators";
    deny (create,modify,delete) user="mallory"
    :Administrators may create and modify lightweight authorities

    View Slide

  4. Access control - evaluation order
    authz.evaluateOrder=deny,allow

    View Slide

  5. Access control - processing (1)
    if (order.equals("deny ,allow"))
    entries = getDenyEntries(acls , op);
    else
    entries = getAllowEntries(acls , op);
    for (ACLEntry entry : entries) {
    if (evaluate(token , entry.getExpressions ()))
    throw new EACLsException("permission␣denied");
    }

    View Slide

  6. Access control - processing (2)
    if (order.equals("deny ,allow"))
    entries = getAllowEntries(acls , op);
    else
    entries = getDenyEntries(acls , op);
    boolean result = false;
    for (ACLEntry entry : entries) {
    if (evaluate(token , entry.getExpressions ()))
    result = true;
    }
    if (! result)
    throw new EACLsException("permission␣denied");

    View Slide

  7. CVE-2018-10801
    1https://access.redhat.com/security/cve/cve-2018-1080

    View Slide

  8. Principle: avoid booleans; use custom types

    View Slide

  9. Principle: avoid booleans; use custom types
    Corollary: use tools that support custom types

    View Slide

  10. Enumeration types: Java, C#, C++11, TypeScript, Python**
    Algebraic data types: Rust, Haskell, Scala, Swift, F#, C++17

    View Slide

  11. Access control - fixed2
    public enum ACLOrder { DenyAllow , AllowDeny };
    public enum ACLEntryType { Allow , Deny };
    public enum ACLResult { Allowed , Denied };
    if (order == EvaluationOrder.DenyAllow) {
    checkDenyEntries(token , acls , op); // throws
    result = checkAllowEntries(token , acls , op);
    } else {
    result = checkAllowEntries(token , acls , op);
    checkDenyEntries(token , acls , op); // throws
    }
    if (result != ACLResult.Allowed)
    throw new EACLsException("permission␣denied");
    2https://frasertweedale.github.io/blog-redhat/posts/2018-03-26-old-dogtag-new-tricks.html

    View Slide

  12. FreeIPA

    View Slide

  13. +-----------------+
    | User |
    | +-------------+ |
    | | krb5 ticket | |
    +-+------|------+-+
    |
    V
    +----------------------------------------+
    | FreeIPA |
    | +-----------------+ +---------------+ |
    | |krb5 proxy ticket| | RA agent cert | |
    +-+-------|---------+--+------|--------+-+
    | |
    V V
    +--------+ +--------+
    | 389 DS |<--------+ Dogtag |
    +--------+ +--------+

    View Slide

  14. CVE-2016-54043
    3https://access.redhat.com/security/cve/cve-2016-5404

    View Slide

  15. Principle: never cut corners on privilege
    separation

    View Slide

  16. CVE-2017-25904
    4https://access.redhat.com/security/cve/cve-2017-2590

    View Slide

  17. Firefox

    View Slide

  18. Object Identifier (OID)
    2.5.4.3 commonName
    2.5.29.14 subjectKeyIdentifier
    1.2.840.113549.1.1.11 sha256WithRSAEncryption

    View Slide

  19. Object Identifier (OID)
    1.3.6.1.4.1.311.21.8.8950086.10656446.2706058.12775672.480128.147.13466065.13029902

    View Slide

  20. python-cryptography (bug) 5,6
    buf_len = 80
    buf = backend._ffi.new("char[]", buf_len)
    res = backend._lib.OBJ_obj2txt(buf , buf_len , obj , 1)
    backend.openssl_assert(res > 0)
    return backend._ffi.buffer(buf , res )[:]. decode ()
    5https://github.com/pyca/cryptography/pull/3612
    6https://bugs.python.org/issue30502

    View Slide

  21. python-cryptography (fixed) 5,6
    buf_len = 80
    buf = backend._ffi.new("char[]", buf_len)
    res = backend._lib.OBJ_obj2txt(buf , buf_len , obj , 1)
    if res > buf_len - 1: # account for null terminator
    buf_len = res + 1
    buf = backend._ffi.new("char[]", buf_len)
    res = backend._lib.OBJ_obj2txt(buf , buf_len , obj , 1)
    backend.openssl_assert(res > 0)
    return backend._ffi.buffer(buf , res )[:]. decode ()
    5https://github.com/pyca/cryptography/pull/3612
    6https://bugs.python.org/issue30502

    View Slide

  22. A buffer length of 80 should be more than enough to handle
    any OID encountered in practice.
    — OBJ_obj2txt(3)7
    7https://www.openssl.org/docs/manmaster/man3/OBJ_nid2ln.html#return_values

    View Slide

  23. Object Identifier (OID)
    0.8.8950086.10656446.2706058.12775672.480128.147.13466065.130299021.3.6.1.4.1.311.21
    .8.8950086.10656446.2706058.12775672.480128.147.13466065.130299021.3.6.1.4.1.311.21
    .8.8950086.10656446.2706058.12775672.480128.147.13466065.130299021.3.6.1.4.1.311.21
    .8.8950086.10656446.2706058.12775672.480128.147.13466065.130299021.3.6.1.4.1.311.21
    .8.8950086.10656446.2706058.12775672.480128.147.13466065.130299021.3.6.1.4.1.311.21
    .8.8950086.10656446.2706058.12775672.480128.147.13466065.130299021.3.6.1.4.1.3

    View Slide

  24. CVE-2017-77928
    8https://access.redhat.com/security/cve/cve-2017-7792

    View Slide

  25. Firefox (bug)9
    char buf [300];
    int i, n = 0, first = 1;
    unsigned long v = 0;
    for (i = 0; i < oid ->len; ++i) {
    v = oid ->data[i];
    if (first)
    n += snprintf (&buf[n], sizeof(buf) - n, "%lu", v);
    else
    n += snprintf (&buf[n], sizeof(buf) - n, ".%lu", v);
    first = 0;
    }
    9https://bugzilla.mozilla.org/show_bug.cgi?id=1368652

    View Slide

  26. Principle: don’t rely on assumptions about input

    View Slide

  27. Principle: don’t rely on assumptions about input
    Corollary: validate / sanitise everything

    View Slide

  28. Principle: use memory-safe languages

    View Slide

  29. An unnamed online service

    View Slide

  30. Authentication - library (v1)
    def authenticate(user , password ):
    ... # talk to the database
    if all_good:
    return True
    else:
    raise AuthenticationError ()

    View Slide

  31. Authentication - application
    user = request[’username’]
    password = request[’password’]
    try:
    authenticate(user , password)
    except AuthenticationError:
    respond_401_unauthorized ()
    # user is logged in
    do_stuff ()

    View Slide

  32. Authentication - library (v2)
    def authenticate(user , password ):
    ... # talk to the database
    if all_good:
    return True
    else:
    return False

    View Slide

  33. Principle: avoid using exceptions

    View Slide

  34. Principle: avoid using exceptions
    Corollary: return types shall express failure cases

    View Slide

  35. Principle: avoid using exceptions
    Corollary: return types shall express failure cases
    Corollary: use tools that ensure failure cases are handled

    View Slide

  36. Error handling in Rust
    enum Result { Ok(T), Err(E) }
    enum Error { AuthError (), ... }
    fn authenticate(user: &str , password: &str)
    -> Result {
    ... // talk to the database
    if all_good {
    return Ok(String :: from(user ));
    } else {
    return Err(Error :: AuthError ());
    }
    }

    View Slide

  37. Wrapping up

    View Slide

  38. Principles
    avoid booleans; use custom types
    never cut corners on privilege separation
    don’t rely on assumptions about input
    use memory-safe languages
    avoid using exceptions

    View Slide

  39. Principle: learn from mistakes

    View Slide

  40. Thank you!
    Except where otherwise noted this work is licensed under
    http://creativecommons.org/licenses/by/4.0/
    https://speakerdeck.com/frasertweedale
    @hackuador

    View Slide

  41. Access control - fixed
    public enum ACLResult { Allowed , Denied };
    List entries;
    if (order == EvaluationOrder.DenyAllow) {
    entries = getDenyEntries(acls , op);
    entries.addAll(getAllowEntries(acls , op));
    } else {
    entries = getAllowEntries(acls , op);
    entries.addAll(getDenyEntries(acls , op));
    }
    for (ACLEntry entry : entries) {
    Optional result = entry.evaluate(token );
    if (result.isPresent ()) return result.get ();
    }
    return ACLResult.Denied;

    View Slide

  42. Access control - fixed
    data RuleOrder = AllowDeny | DenyAllow
    data RuleType = Allow | Deny deriving (Eq)
    data Result = Allowed | Denied
    eval :: RuleOrder -> Token -> Op -> [Rule] -> Result
    eval order tok op rules =
    fromMaybe Denied
    (alaf First foldMap (evalRule tok) orderedRules)
    where
    opRules = filter (elem op . aclRulePermissions) rules
    allowRules = filter ((== Allow) . aclRuleType) opRules
    denyRules = filter ((/= Allow) . aclRuleType) opRules
    orderedRules = case order of
    DenyAllow -> denyRules <> allowRules
    AllowDeny -> allowRules <> denyRules

    View Slide