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

Making Web Development "Secure By Default"

Making Web Development "Secure By Default"

Adam Goodman, Principal Security Architect at Duo Security

Duo Security

June 02, 2014
Tweet

More Decks by Duo Security

Other Decks in Technology

Transcript

  1. Making Web Development “Secure By Default” ! Adam Goodman 2014-05-31

  2. The OWASP Top 10 2004: • Unvalidated Input • Broken

    Access Control • Broken Authentication and Session Management • Cross Site Scripting • Buffer Overflow • Injection • Improper Error Handling • Insecure Storage • Application Denial of Service • Insecure Configuration Management
  3. The OWASP Top 10 2004: • Unvalidated Input • Broken

    Access Control • Broken Authentication and Session Management • Cross Site Scripting • Buffer Overflow • Injection • Improper Error Handling • Insecure Storage • Application Denial of Service • Insecure Configuration Management 2013: • Injection • Broken Authentication and Session Management • Cross Site Scripting • Insecure Direct Object References • Security Misconfiguration • Sensitive Data Exposure • Missing Function Level Access Control • Cross-Site Request Forgery • Using Components with Known Vulnerabilities • Unvalidated Redirects and Forwards
  4. None
  5. 2004: • Unvalidated Input • Broken Access Control • Broken

    Authentication and Session Management • Cross Site Scripting • Buffer Overflow • Injection • Improper Error Handling • Insecure Storage • Application Denial of Service • Insecure Configuration Management 2013: • Injection • Broken Authentication and Session Management • Cross Site Scripting • Insecure Direct Object References • Security Misconfiguration • Sensitive Data Exposure • Missing Function Level Access Control • Cross-Site Request Forgery • Using Components with Known Vulnerabilities • Unvalidated Redirects and Forwards Success Story: Buffer Overflow
  6. Buffer Overflow - Review void bad_idea(const char *input) {! char

    buf[10];! strcpy(buf, input);! /* ... */! }! ! int main(void) {! bad_idea("This is a longish string");! return 0;! }!
  7. Buffer Overflow - Review void less_bad_idea(const char *input) {! char

    buf[10];! strlcpy(buf, input, sizeof(buf));! /* ... */! }! ! int main(void) {! less_bad_idea(“This is a longish string");! return 0;! }!
  8. Microsoft SDL http://blogs.msdn.com/b/bryang/archive/2011/04/01/security-development-lifecycle.aspx

  9. Best Practices • “Deprecate Unsafe Functions” - no more strcpy,

    strcat, … • Training • Code reviews • Automated enforcement (framework changes, analysis tools, …)
  10. Compiler Smarts void less_bad_idea(const char *input) {! char buf[10];! /*

    MSVC 2005 and newer; C++ only */! strcpy_s(buf, input);! /* ... */! }! ! ! (Similar: FORTIFY_SOURCE in gcc)
  11. Exploit Mitigation Make it less feasible to exploit bugs (i.e.

    turn “security bugs” back into “ordinary bugs”): • Stack Smashing Protection (SSP) • Data Execution Prevention (DEP / NX) • Address Space Layout Randomization (ASLR)
  12. Encapsulate Hazardous Code We don’t write web apps in C/C++

    anymore. ! Most of our high-level languages and web servers are still built on C, but these are carefully-curated components written by skilled developers with lots of review (we hope!).
  13. (We’re Not There Quite Yet) http://xkcd.com/1354/

  14. To Review Hypothesis: Buffer overflows fell off the OWASP Top

    10 thanks to • Concerted efforts to define and (automatically!) detect anti- patterns • Better tooling to simplify code / limit human error • Catch-all exploit mitigation technologies • The simple fact that we don’t build web apps in C/C++ anymore!
  15. To Review Hypothesis: Buffer overflows fell off the OWASP Top

    10 thanks to: • Concerted efforts to define and (automatically!) detect anti- patterns • Better tooling to simplify code / limit human error • Catch-all exploit mitigation technologies • The simple fact that we don’t build web apps in C/C++ anymore! ! How can we apply these ideas to other classes of bugs?
  16. 2004: • Unvalidated Input • Broken Access Control • Broken

    Authentication and Session Management • Cross Site Scripting • Buffer Overflow • Injection • Improper Error Handling • Insecure Storage • Application Denial of Service • Insecure Configuration Management 2013: • Injection • Broken Authentication and Session Management • Cross Site Scripting • Insecure Direct Object References • Security Misconfiguration • Sensitive Data Exposure • Missing Function Level Access Control • Cross-Site Request Forgery • Using Components with Known Vulnerabilities • Unvalidated Redirects and Forwards XSRF
  17. XSRF Review 1. Alice logs into https://mybank.com, and gets back

    a session cookie:
 
 200 OK
 Set-Cookie: session-id=123-456789; path=/; domain=.mybank.com; Secure; HttpOnly;
 2. Alice is tricked into opening https://evilsite.com, whose JavaScript code sends a POST to mybank.com:
 
 POST /transfer_funds
 Cookie: session-id=123-456789
 ...
 destination=evil_account_number&amount=100000&currency=USD
  18. 1. https://mybank.com sends back another cookie with an “xsrf token”:


    
 200 OK
 Set-Cookie: session-id=123-456789; path=/; domain=.mybank.com; Secure; HttpOnly;
 Set-Cookie: _xsrf=SOMESECRETVALUE; path=/; domain=.mybank.com; Secure; HttpOnly; 
 2. On any page with a form, https://mybank.com includes the same token in an input field to be POST-ed: … <input type='hidden' name='_xsrf' value='SOMESECRETVALUE'>
 … XSRF Tokens
  19. 3. https://mybank.com rejects any POST that without an XSRF token,

    or in which the token doesn’t match the Cookie XSRF Tokens
  20. XSRF Tokens Elegant solution: • Requires no new server-side state

    • Can be added to most existing web applications with minor modifications • “Secure by default”
  21. 2004: • Unvalidated Input • Broken Access Control • Broken

    Authentication and Session Management • Cross Site Scripting • Buffer Overflow • Injection • Improper Error Handling • Insecure Storage • Application Denial of Service • Insecure Configuration Management 2013: • Injection • Broken Authentication and Session Management • Cross Site Scripting • Insecure Direct Object References • Security Misconfiguration • Sensitive Data Exposure • Missing Function Level Access Control • Cross-Site Request Forgery • Using Components with Known Vulnerabilities • Unvalidated Redirects and Forwards XSS
  22. XSS - Review {% autoescape None %} ! <html> <body>

    <h1>Your Notes</h1> {% for row in rows %} <hr> <p> {{ row.content }} </p> {% end %} </body> </html>
  23. Threats • Annoy users (i.e. <script>alert(‘hi’)</script>) • Steal any data

    in the DOM • (Including XSRF tokens!) • Phish users’ credentials, even if it wasn’t a login page! XSS - Review
  24. XSS - Escape All The Things {% autoescape None %}

    ! <html> <body> <h1>Your Notes</h1> {% for row in rows %} <hr> <p> {{ escape(row.content) }} </p> {% end %} </body> </html>
  25. XSS - Autoescape • Actually, Tornado does auto-escape by default

    (I had to disable it!) • But, naive auto-escaping is not good enough!
  26. Different Contexts {% autoescape None %} ! <html> <head> <title>Hello,

    World</title> <script> var qux = {{ json_encode(qux) }}; </script> </head> <body> <input type="hidden" name="foo" value="{{ escape_attr(foo) }}" /> <a href="/{{ url_escape(bar) }}">{{ escape(baz) }}</a> </body> </head>
  27. Context-Aware Auto-Escaping Basic idea: as you’re generating template output, feed

    it back through an HTML parser. When you hit a template directive, figure out what context you’re in, and call the appropriate escaping function!
  28. Mitigation: Content-Security-Policy (CSP) HTTP Header that will tell the browser

    from what sources it’s allowed to load (and in the case of scripts, execute) content. •Content-Security-Policy: default-src ‘self' - load scripts/ images/etc. only from the same domain (and do not run inline scripts or process inline CSS!) •Content-Security-Policy: default-src 'self'; img-src * - same, except allow loading images from any host For more, see: http://cspisawesome.com
  29. Mitigation: Content-Security-Policy (CSP) • Turns security vulnerabilities back into “ordinary

    bugs”… • (… if your users are using supported browsers!) • Eliminating inline scripts usually requires some restructuring • but separating code, data, and presentation is a good pattern anyway, right? :)
  30. 2004: • Unvalidated Input • Broken Access Control • Broken

    Authentication and Session Management • Cross Site Scripting • Buffer Overflow • Injection • Improper Error Handling • Insecure Storage • Application Denial of Service • Insecure Configuration Management 2013: • Injection • Broken Authentication and Session Management • Cross Site Scripting • Insecure Direct Object References • Security Misconfiguration • Sensitive Data Exposure • Missing Function Level Access Control • Cross-Site Request Forgery • Using Components with Known Vulnerabilities • Unvalidated Redirects and Forwards SQL Injection
  31. SQL Injection - Review class LoginHandler(tornado.web.RequestHandler): def post(self): user =

    self.get_argument('username') password = self.get_argument('password') ! pwhash = hashlib.sha1(password).hexdigest(); row = self.application.db.get( 'SELECT uid FROM users WHERE uname=\'%s\' AND password=\'%s\'' % (user, pwhash)) if row: self.set_secure_cookie('user', str(row.uid)) self.redirect('/')
  32. SQL Injection - Review class LoginHandler(tornado.web.RequestHandler): def post(self): user =

    self.get_argument('username') password = self.get_argument('password') ! pwhash = hashlib.sha1(password).hexdigest(); row = self.application.db.get( 'SELECT uid FROM users WHERE uname=\'%s\' AND password=\'%s\'' % (user, pwhash)) if row: self.set_secure_cookie('user', str(row.uid)) self.redirect('/') ! ! (By the way, DO NOT store your passwords like this!)
  33. Fun things to submit for ‘user’: • akgood' OR '1'

    = '1 • akgood'; DROP TABLE users; SELECT … • or just point a tool like sqlmap (http://sqlmap.org/) at it! SQL Injection - Review
  34. Parameterized Queries class LoginHandler(tornado.web.RequestHandler): def post(self): user = self.get_argument('username') password

    = self.get_argument('password') ! pwhash = hashlib.sha1(password).hexdigest(); row = self.application.db.get( 'SELECT uid FROM users WHERE uname=%s AND password=%s', user, pwhash) if row: self.set_secure_cookie('user', str(row.uid)) self.redirect('/') ! ! Can you see the difference?!
  35. ORM class LoginHandler(tornado.web.RequestHandler): def post(self): username = self.get_argument('username') password =

    self.get_argument('password') ! pwhash = hashlib.sha1(password).hexdigest(); rows = self.application.session.query( User).filter_by(uname=username, password=pwhash) if rows: row = rows[0] self.set_secure_cookie('user', str(row.uid)) self.redirect('/')
  36. ORM Magic from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column,

    Integer, String ! Base = declarative_base() class User(Base): __tablename__ = 'users' ! uid = Column(Integer, primary_key=True) uname = Column(String) password = Column(String)
  37. Middle Ground: SQL Expression API class LoginHandler(tornado.web.RequestHandler): def post(self): username

    = self.get_argument('username') password = self.get_argument('password') ! pwhash = hashlib.sha1(password).hexdigest(); s = select([users]).where( (users.c.uname == username) & (users.c.password == pwhash)) rows = self.application.conn.execute(s) if rows: row = rows[0] self.set_secure_cookie('user', str(row['uid'])) self.redirect(‘/') ! … ! users = Table('users', meta, autoload=True, autoload_with=engine)

  38. Static Analysis If you really must write raw SQL: •

    basic: a check to ensure that developers never use the string interpolation operator (‘%’) in a database function call • better: dataflow analysis to trace the construction of a query string and ensure no untrusted inputs were used (a.k.a. ‘taint analysis’)
  39. Static Analysis: Commercial Solutions Powerful, but extremely expensive - e.g.:

    • Veracode • Coverity • Fortify
  40. Static Analysis: Homegrown Hacks Example: make sure that we only

    ever use Python’s “SystemRandom” class to generate random values
 
 v1: basically, grep for instances of: • ‘random\.\w+’ (other than ‘random.SystemRandom) • ‘from random import .*’
 (other than ‘from random import SystemRandom) v2: use the python AST
  41. Abstract Syntax Tree >>> import ast >>> m = ast.parse("from

    random import SystemRandom") >>> ast.dump(m) "Module(body=[ImportFrom(module='random', names=[alias(name='SystemRandom', asname=None)], level=0)])" >>> m.body[0].module ‘random' ! >>> m2 = ast.parse("self.db.execute('SELECT * FROM users WHERE uname=%s' % (uname))") >>> ast.dump(m2) "Module(body=[Expr(value=Call(func=Attribute(value=Attribute(value=Name(id='self' , ctx=Load()), attr='db', ctx=Load()), attr='execute', ctx=Load()), args=[BinOp(left=Str(s='SELECT * FROM users WHERE uname=%s'), op=Mod(), right=Name(id='uname', ctx=Load()))], keywords=[], starargs=None, kwargs=None))])"
  42. Checking SystemRandom with the AST class RandomVisitor(ast.NodeVisitor): def visit_Attribute(self, node):

    if (isinstance(node.value, ast.Name) and node.value.id == 'random' and node.attr != 'SystemRandom'): raise BadRandomGenerator(node.lineno) ! def visit_ImportFrom(self, node): if (node.module == 'random' and any(alias.name != 'SystemRandom' for alias in node.names)): raise BadRandomGenerator(node.lineno) ! with open(some_python_module, 'r') as fp: m = ast.parse(fp.read()) RandomVisitor().visit(m)
  43. • Use frameworks and tools that prevent entire classes of

    bugs by default - either by intentionally mitigating vulnerabilities or simply by encapsulating dangerous code so you don’t have to deal with it. • If you see an anti-pattern, write a script to enforce it! • Can be quite basic, especially if you pair it with peer code reviews and consistent coding norms • Don’t forget about the rest of the SDL Conclusions
  44. Thanks! akgood@duosecurity.com @akgood