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 full-size slide

  2. 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 full-size slide

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

    View full-size slide

  4. 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 full-size slide

  5. 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 full-size slide

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

    View full-size slide

  7. Principle: avoid booleans; use custom types

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  10. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

  13. Principle: never cut corners on privilege
    separation

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  16. Object Identifier (OID)
    1.3.6.1.4.1.311.21.8.8950086.10656446.2706058.12775672.480128.147.13466065.13029902

    View full-size slide

  17. 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 full-size slide

  18. 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 full-size slide

  19. 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 full-size slide

  20. 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 full-size slide

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

    View full-size slide

  22. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

  25. Principle: use memory-safe languages

    View full-size slide

  26. An unnamed online service

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  30. Principle: avoid using exceptions

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  33. 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 full-size slide

  34. 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 full-size slide

  35. Principle: learn from mistakes

    View full-size slide

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

    View full-size slide

  37. 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 full-size slide

  38. 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 full-size slide