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. 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
  2. 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"); }
  3. 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");
  4. 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
  5. +-----------------+ | User | | +-------------+ | | | krb5

    ticket | | +-+------|------+-+ | V +----------------------------------------+ | FreeIPA | | +-----------------+ +---------------+ | | |krb5 proxy ticket| | RA agent cert | | +-+-------|---------+--+------|--------+-+ | | V V +--------+ +--------+ | 389 DS |<--------+ Dogtag | +--------+ +--------+
  6. 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
  7. 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
  8. 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
  9. 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
  10. Authentication - library (v1) def authenticate(user , password ): ...

    # talk to the database if all_good: return True else: raise AuthenticationError ()
  11. Authentication - application user = request[’username’] password = request[’password’] try:

    authenticate(user , password) except AuthenticationError: respond_401_unauthorized () # user is logged in do_stuff ()
  12. Authentication - library (v2) def authenticate(user , password ): ...

    # talk to the database if all_good: return True else: return False
  13. Principle: avoid using exceptions Corollary: return types shall express failure

    cases Corollary: use tools that ensure failure cases are handled
  14. Error handling in Rust enum Result <T, E> { Ok(T),

    Err(E) } enum Error { AuthError (), ... } fn authenticate(user: &str , password: &str) -> Result <String , Error > { ... // talk to the database if all_good { return Ok(String :: from(user )); } else { return Err(Error :: AuthError ()); } }
  15. 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
  16. Thank you! Except where otherwise noted this work is licensed

    under http://creativecommons.org/licenses/by/4.0/ https://speakerdeck.com/frasertweedale @hackuador
  17. Access control - fixed public enum ACLResult { Allowed ,

    Denied }; List <ACLEntry > 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 <ACLResult > result = entry.evaluate(token ); if (result.isPresent ()) return result.get (); } return ACLResult.Denied;
  18. 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