Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Applications secure by default


Published on

The security of an application is a continuous struggle between solid proactive controls and quality in SDLC versus human weakness and resource restrictions. As the pentester's experience confirms, unfortunatelly even in high-risk (e.g. banking) applications, developed by recognized vendors, the latter often wins - and we end up with critical vulnerabilities.
One of the primary reasons is lack of mechanisms enforcing secure code by default, as opposed to manual adding security per each function. Whenever the secure configuration is not default, there will almost inevitably be bugs, especially in complex systems.
I will pinpoint what should be taken into consideration in the architecture and design process of the application. I will show solutions that impose security in ways difficult to circumvent unintentionally by creative developers. I will also share with the audience the pentester's (=attacker's) perspective, and a few clever tricks that made the pentest
(=attack) painful, or just rendered the scenarios irrelevant.

Published in: Software
  • Be the first to comment

Applications secure by default

  1. 1. Platinum Sponsors: #DevoxxPL Applications secure by default Sławomir Jasek
  2. 2. Pentester / security consultant. Assessments and consultancy regarding security of various applications - web, mobile, embedded, ... Since 2003 / over 400 systems and applications Sławomir Jasek
  3. 3. Code insecure by default Blacklisting vs whitelisting Features vs security Access control Beware the "silver bullets" Fight back! Devops The Takeaway Agenda
  5. 5. $url = ''; $result = file_get_contents($url); Is there anything wrong? The default setting does not verify hostname => Man in the Middle
  6. 6. $url = ''; $contextOptions = array( 'ssl' => array( 'verify_peer' => true, 'cafile' => '/etc/ssl/certs/ca-certificates.crt', 'verify_depth' => 5, 'CN_match' => '', 'disable_compression' => true, 'SNI_enabled' => true, 'ciphers' => 'ALL!EXPORT!EXPORT40!EXPORT56!aNULL!LOW!RC4' ) ); $sslContext = stream_context_create($contextOptions); $result = file_get_contents($url, NULL, $sslContext); The proper way Defaults to false!
  7. 7. file_get_contents(https language:php
  8. 8. file_get_contents verify_peer Only 1 programmer in 51 uses verify_peer options. Often to explicitly disable it ;)
  9. 9. The default value changed only recently in PHP 5.6.0. But there is hope... All the previous versions susceptible to Man-In-The-Middle attacks.
  10. 10. $url = ''; $req = curl_init($url); curl_setopt($req, CURLOPT_RETURNTRANSFER, TRUE); $result = curl_exec($req); Curl - secure by default
  11. 11. // Open SSLSocket directly SocketFactory sf = SSLSocketFactory.getDefault(); SSLSocket socket = (SSLSocket) sf.createSocket("", 443); SSLSession s = socket.getSession(); // ... use socket ... socket.close(); Java: SSL SocketFactory
  12. 12. // Open SSLSocket directly SocketFactory sf = SSLSocketFactory.getDefault(); SSLSocket socket = (SSLSocket) sf.createSocket("", 443); SSLSession s = socket.getSession(); // Verify that the certicate hostname is for HostnameVerifier hv = HttpsURLConnection.getDefaultHostnameVerifier(); if (!hv.verify("", s)) { throw new SSLHandshakeException("Expected, " "found " + s.getPeerPrincipal()); } // ... use socket ... socket.close(); SSL SocketFactory
  13. 13. And the docs do not help Pentester’s experience: all tested Android apps using SSLSocket were vulnerable. Despite the bold warnings and proper example code...
  14. 14. URL url = new URL(""); URLConnection urlConnection = url.openConnection(); InputStream in = urlConnection.getInputStream(); copyInputStreamToOutputStream(in, System.out); You need to explicitly disable verification. Pentester’s experience: only a few tested Android apps using urlConnection were deliberately broken. HTTPS – the proper way
  16. 16. Attack scenario: http://localhost/foo/bar.action?<script>alert(1)</script> Lack of proper output encoding. Intruder runs hostile javascript in a victim’s browser (aka „Cross Site Scripting”). Struts 2 – XSS vulnerability
  17. 17. --- struts2/trunk/core/src/main/java/org/apache/struts2/views/util/ 2008/01/24 07:37:32 614813 +++ struts2/trunk/core/src/main/java/org/apache/struts2/views/util/ 2008/01/24 07:39:45 614814 @@ -174,10 +174,14 @@ buildParametersString(params, link, "&"); } - String result; + String result = link.toString(); + + if (result.indexOf("<script>") >= 0){ + result = result.replaceAll("<script>", "script"); + } Fix - blacklisting
  18. 18. Attack 1: /myAction.action?param"><sCript>alert('XSS');</sCript>=1 This is very similiar to the vulnerability in Security Bulletin S2-002; however, the implemented fix for S2-002 only checks for "<script>", not "<sCript>". Attack 2: /myAction.action?param"onMouseOver%3D"javascript:alert('XSS');">=1 Simply checking for <script> isn't sufficient because certain attributes can be injected to execute javascript. In attack 2, the user simply has to hover over the link with their mouse and arbitrary javascript will be executed. 2 years later...
  19. 19. -builder.append(name) +builder.append(translateAndEncode(name)) That was 19 characters - exactly 4.78 times less than 91 characters used in first, unsuccessful „blacklist” fix. Final fix
  20. 20. We found an XSS vulnerability, as usually recommended to properly encode relevant characters in the output context. Retest #1: <script> does not work, but <Script> does ;) Retest #2: <Script> nor <sCript> does not work. But onclick does. Retest #3: onclick does not work, but onmouseover does. Retest #4: onmouseover fixed, but onmousedown not ;) Retest #5: ... From the pentester's diary
  21. 21. +------------------------+ | XSS_PAYLOAD | +------------------------+ | onclick= | | ondblclick= | | onmousedown= | | onmousemove= | | onmouseover= | | onmouseout= | | onmouseup= | | onkeydown= | | onkeypress= | | onkeyup= | | onabort= | | onerror= | | onload= | | onresize= | Finally, we broke into database via sql-injection | onscroll= | | onunload= | | onblur= | | onchange= | | onfocus= | | onreset= | | onselect= | | onsubmit= | | onevent= | | <script | | script> | | <svg | | svg> | | javascript: | | <iframe | | iframe> | | <form | | form> | | <input | | ''iframe'' | | "iframe" | | document.createelement | | string.fromcharcode( | | <img/src | | submit() | | document.location. | | alert( | | <img | | <vbscript | ...
  22. 22. Fix: remove ../ Proper way: whitelist of allowed characters. Path traversal
  24. 24. Define user # fields: [:id, :first, :last, :email, :admin] class User < ActiveRecord::Base End RoR – user edit form User edit form user = User.find(params[:id]) user.update_attributes(params[:user])
  25. 25. PUT[first]=Slawomir &user[last]=Jasek&user[email]=slawomir.jasek RoR – user edit form
  26. 26. PUT[first]=Slawomir &user[last]=Jasek&user[email]=slawomir.jasek[admin]=true RoR – mass assignment
  27. 27. You were supposed to manually add whitelisted: class User < ActiveRecord::Base attr_accessible :first, :last, :email end or blacklisted parameters: class User < ActiveRecord::Base attr_protected :admin end RoR – mass assignment (2012)
  28. 28. GitHub PWND
  29. 29. Change default value of global config parameter turning the mass assignment off: # Enforce whitelist mode for mass assignment. # This will create an empty whitelist of attributes available for mass- assignment for all models # in your app. As such, your models will need to explicitly whitelist or blacklist accessible # parameters by using an attr_accessible or attr_protected declaration. <%= comment_if :skip_active_record %> config.active_record.whitelist_attributes = true Fix - evolution
  30. 30. config.active_record.mass_assignment_sanitizer It will raise an ActiveModel::MassAssignmentSecurity::Error any time your application attempts to mass-assign something it shouldn't. RoR 3.2: Mass assignment sanitizer
  31. 31. Secure by default. You must first call permit on the params hash with the keys that are allowed for a specific action: # Require that :user be a key in the params Hash, # and only accept whitelisted attributes def user_params params.require(:user).permit(:first, :last, :email) end RoR 4: Strong Parameters
  32. 32. Before: <%=HTMLEncoder.encode(((Person)person).getAd dress().getStreet())%> After (EL): <c:out value="person.address.street"/> Expression language – starring major frameworks
  33. 33. DEMO - The security impact
  34. 34. Major frameworks: Spring, JBoss SEAM, Struts... Easy to detect, automatic tools to exploit remotely into shell. The "no man's land". (as I have pointed out in 2012) Expression Language flaws
  35. 35. 2003: parameter names like: @System@exit(1) "Patrick says he has fixed it" ;) 2008.06: the # can be encoded in u0023 ('u0023' + 'session['user'])(unused)=0wn3d Released Xwork 2.0.5 – blacklisted the attack 2008.10: removed space characters and the exploit still works ;) ('u0023'+'session['user'])(unused)=0wn3d The fix - features vs security
  36. 36. 2010: You can access the context and turn the settings on: http://mydomain/MyStruts.action?('u0023_memberAccess['allowStatic MethodAccess']')(meh)=true&(aaa)(('u0023context['xwork.MethodAcc essor.denyMethodExecution']u003du0023foo')(u0023foou003dnew%20 java.lang.Boolean("false")))&(asdf)(('u0023rt.exit(1)')(u0023rtu 003d@java.lang.Runtime@getRuntime()))=1 Fix: whitelist allowed chars in parameter names 2011: User input is evaluated as an OGNL expression when there's a conversion error. 2011: The problem concerns not only parameters, but also cookie values The fix – continued...
  37. 37. 2012: fix based on whitelisting acceptable parameter names closed the vulnerability only partially. Still possible RCE with slightly modified attack syntax. Fix: deny evaluation in parameter names. 2013: The second evaluation happens when redirect result reads it from the stack and uses the previously injected code as redirect parameter. I won't reveal all the following episodes, I encourage you to read on: There's a lot of action! Struts.action. The fix – continued
  38. 38. data=%3C%3Fxml+version%3D%221.0%22%3F%3E%0A%3Cuser%3E%0A%C2%A0%C 2%A0%C2%A0%C2%A0%3Cfirstname%3EJan%3C%2Ffirstname%3E%0A%C2%A0%C2 %A0%C2%A0%C2%A0%3Clastname%3EKowalski%3C%2Flastname%3E%0A%3C%2Fu ser%3E HTML decoded: <?xml version="1.0"?> <user> <firstname>Jan</firstname> <lastname>Kowalski</lastname> </user> XML parsing
  39. 39. <?xml version="1.0"?> <!DOCTYPE lolz [ <!ENTITY lol "lol"> <!ELEMENT lolz (#PCDATA)> ]> <lolz>&lol;</lolz> <lolz>lol</lolz> XML parsing: DOCTYPE
  40. 40. <?xml version="1.0"?> <!DOCTYPE lolz [ <!ENTITY lol "lol"> <!ENTITY lol1 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;"> <!ELEMENT lolz (#PCDATA)> ]> <lolz>&lol1;</lolz> <lolz>lollollollollollollollollollol</lolz> XML parsing: DOCTYPE
  41. 41. <?xml version="1.0"?> <!DOCTYPE lolz [ <!ENTITY lol "lol"> <!ELEMENT lolz (#PCDATA)> <!ENTITY lol1 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;"> <!ENTITY lol2 "&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;"> <!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;"> <!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;"> <!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;"> <!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;"> <!ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;"> <!ENTITY lol8 "&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;"> <!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;"> ]> <lolz>&lol9;</lolz> <user> <firstname>Jan</firstname> <lastname>Kowalski</lastname> </user> "The Man Who Laughs"
  42. 42. ROTFL TO DEATH!!!
  43. 43. <!ENTITY xxe SYSTEM "file:///etc/passwd" >]> <foo>&xxe;</foo> As seen in Google, Facebook, Ebay and about 80% of tested applications processing user input as XML. It may be even worse
  44. 44. FB XXE
  45. 45. Technology Default DTD processing value .NET 4 settings.DtdProcessing = DtdProcessing.Prohibit; .NET 3.5 ProhibitDtd in XmlReaderSettings is true, but ProhibitDtd in XmlTextReader is false LibXML2 (C++ ) starting with libxml2 version 2.9 (2012), XXE has been disabled by default NSXMLDocument (iOS) iOS5 and later: Only entities that don't require network access are loaded. iOS4 and earlier: All external entities are loaded by default. Xerces 2 disallow-doctype-decl=false Xerces 1 external-general-entities=true PHP Have to manually disable: libxml_disable_entity_loader(true); Default DTD processing in various parsers
  47. 47. Access control: typical scenario
  48. 48. Student tries to invoke administrative functions GET /course/quiz/solutions GET /admin/course/cancel Student tries to access restricted data GET /course/view/42 Student tries to alter his account rights. POST /user/edit roles=[student,admin] Access control: typical attack
  49. 49. <security:intercept-url pattern="/user/add" access="hasRole('ROLE_ADMIN') or hasRole('ROLE_MANAGER')" /> <security:intercept-url pattern="/user/view" access="hasRole('ROLE_ADMIN') or hasRole('ROLE_MANAGER') or hasRole('ROLE_PRINCIPAL') or hasRole('ROLE_TEACHER') or hasRole('ROLE_STUDENT')" /> Approach 1: Spring Security
  50. 50. It works for simple apps, with unsophisticated role policies (e.g. separated admin interface). Will not work for: POST /user/edit HTTP/1.1 task=MODIFY_RIGHTS&id=34 Has concept of „roles”, but out of the box does not have concept of „permissions”. Spring ACL on the other hand is too complex. Does not help a lot with access to specific instance (e.g. other user’s data) Spring Security
  51. 51. Complex hard-coded checks in application code, needed to be manually loaded to every endpoint: If (( user.isRole('ROLE_ADMIN') || user.isRole('ROLE_MANAGER') || user.isRole('ROLE_PRINCIPAL') ) || ( user.isRole('ROLE_TEACHER') && user.isTeacher(course) ) || ( user.isRole('ROLE_STUDENT') && (user.isStudent(course) ) ...and you will probably end-up with:
  52. 52. One simple "if" statement, permissions separated from roles. if ( currentUser.isPermitted("users:add") ) { //add an user } int courseId=request.getInt("course") if ( currentUser.isPermitted("courses:view:"+courseId) ) { //show contents of course } Apache Shiro – permission based access control
  53. 53. Real story: account history – select your account
  54. 54. The REST API request GET /services/history/account/85101022350445200448009906 HTTP/1.1 SA-DeviceId: 940109f08ba56a89 SA-SessionId: 826175 Accept: application/json Host: acc Connection: Keep-Alive User-Agent: Apache-HttpClient/UNAVAILABLE (java 1.4)
  55. 55. The REST API request GET /services/history/account/45101022350445200448005388 HTTP/1.1 SA-DeviceId: 940109f08ba56a89 SA-SessionId: 826175 Accept: application/json Host: acc Connection: Keep-Alive User-Agent: Apache-HttpClient/UNAVAILABLE (java 1.4) Change the acc number -> get other user’s data
  56. 56. John: <select name="account"> <option value=0 >85101022350445200448009906</option> <option value=1 >34101022350445200448009905</option> <option value=2 >41101022350445200448009904</option> </select> Mary: <select name="account"> <option value=0 >45101022350445200448005388</option> <option value=1 >31101022350445200448005390</option> </select> The better way? "Local" ID mapped server-side into real values
  57. 57. The better way? SA-DeviceId: d4c79a0fd994b1f3 SA-SessionId: 850073 GET /services/history/account/1 HTTP/1.1 SA-DeviceId: 940109f08ba56a89 SA-SessionId: 826175 Accept: application/json Host: acc Connection: Keep-Alive User-Agent: Apache-HttpClient/UNAVAILABLE (java 1.4) It is(?) by design not possible to attack other user's data.
  58. 58. Would be great if not... John: GET /services/history/account/2 HTTP/1.1 SA-DeviceId: 940109f08ba56a89 SA-SessionId: 826175 Accept: application/json Host: acc Connection: Keep-Alive API just works as a proxy to backend system. Default session mechanism from backend – just incrementing IDs ;) Mary: GET /services/history/account/2 HTTP/1.1 SA-DeviceId: d4c79a0fd994b1f3 SA-SessionId: 826179 Accept: application/json Host: acc Connection: Keep-Alive Trivial to guess other user's sessionId
  59. 59. Automatically encrypts paths with individual, session- based and unique key: TkeuxlXeeVypcehJ8HQgNbj(...) Apache Wicket
  60. 60. The browser works on rendered GUI elements, POSTs to the server event-driven actions (e.g. mouse clicks in certain location). It is by design not possible to invoke actions that are not in GUI. POST /dashboard/UIDL/?v-uiId=0 HTTP/1.1 Host: {"csrfToken":"981b1cd6-66df-481c-9d6e- 6c293eb70ea3","rpc":[["283","v","v",["positionx",["i","440"]]],["283"," v","v",["positiony",["i","139"]]],["309","com.vaadin.shared.ui.button.B uttonServerRpc","click",[{"altKey":false,"relativeX":"42","relativeY":" 23","ctrlKey":false,"button":"LEFT","shiftKey":false,"clientX":"1109"," clientY":"598","metaKey":false,"type":"1"}]]],"syncId":14} Vaadin
  61. 61. BEWARE
  62. 62. There's no silver bullet! Hadi Hariri keynote on Monday
  63. 63. Automagic output encoding by framework A framework automatically encodes special chars into HTML: <bean:write name="transferFormId" property="trn_recipient"/> ATTACK: trn_recipient="<script>alert('xss')</script> <input type="text" name="trn_recipient" value="&quot;&lt;script&gt;alert('xss')&lt;/script&gt;"
  64. 64. Beware the security mechanism use cases But unfortunatelly that does not help when you put a value from end-user directly into javascript context: <script> var split='<bean:write name="transferFormId" property="trn_recipient">'; splitRecipient(split); </script> ATTACK: trn_recipient=';alert('xss');-- <script> var split='';alert('xss');--
  65. 65. Encode special chars properly in context! • HTML element • HTML atribute • JavaScript • JSON • CSS / style • URL Encode properly
  66. 66. Prepared statement / call String sql = "select * from users where firstname=? and lastname=?"; query = conn.prepareStatement(sql); firstname = request.getParameter("first"); lastname = request.getParameter("last"); query.setString(1, firstname); query.setString(2, lastname); result = query.executeQuery(); String sql = "{call" + "?" + ", ?)}"; call = conn.prepareCall(sql); firstname = request.getParameter("first"); lastname = request.getParameter("last"); call.setString(1, firstname); call.setString(2, lastname); call.execute(); prepared statement prepared call
  67. 67. Called stored procedure PROCEDURE search( p_firstname IN T_STRING, p_lastname IN T_STRING, ) IS (...) v_sql_select := ' SELECT distinct a.USER_ID'; v_sql_from := ' FROM APP_WEB.USERS a '; v_sql_where := ' WHERE a.USER_ID is not null '; IF p_firstname is not null THEN v_sql_where := v_sql_where || ' and lower(trim(a.FIRSTNAME)) = lower(trim(' || P_FIRSTNAME || ')) '; END IF;
  68. 68. SQL injection inside stored procedure PROCEDURE search( p_firstname IN T_STRING, p_lastname IN T_STRING, ) IS (...) v_sql_select := ' SELECT distinct a.USER_ID'; v_sql_from := ' FROM APP_WEB.USERS a '; v_sql_where := ' WHERE a.USER_ID is not null '; IF p_firstname is not null THEN v_sql_where := v_sql_where || ' and lower(trim(a.FIRSTNAME)) = lower(trim('|| 'adam')) union select version,'x' from v$instance-- || ')) '; END IF;
  69. 69. NoSQL. There is no sql injection. There is nosql injection! Instead of ' OR 1=1 -- try a'; return 1=1; var dummy='a NoSQL
  70. 70. Change SMS authorization phone number in internet banking application Application logic flaws
  71. 71. What could possibly go wrong? Change SMS authorization phone number in internet banking application
  72. 72. The scenario missed in functional tests Change SMS authorization phone number in internet banking application
  73. 73. The evil wins The application did verify only the SMS code from the new phone. And intruder with access to user session can take over the authorization.
  74. 74. FIGHT BACK! - Intrusion detection and prevention
  75. 75. • Input validation server-side when client-side validation exists • Non-user editable parameters/values (hidden fields, checkboxes, radio buttons, select lists) • Forced browsing to common attack entry points or honeypot URL (e.g. in robots.txt) • Obvious sqli, xss inj attacks • Workflow sequence abuse Intrusion detection – level basic slide 60
  76. 76. One of the most annoying experiences during test was automatic logout on every test-case in application that required manual interaction to authenticate. Active intrusion prevention
  77. 77. OWASP AppSensor Conceptual framework and methodology to implement intrusion detection and automated response into applications. OWASP mod_security core ruleset An easily "pluggable" set of generic attack detection rules that provide a base level of protection for any web application. Basic tools
  78. 78. DEVOPS - Default infrastructure
  79. 79. Default error handling Reveals information on used components, helps to attack known vulnerabilities
  80. 80. Trading application – binary protocol
  81. 81. Trading application – binary protocol
  82. 82. Trading application – binary protocol
  83. 83. And what if we...
  84. 84. And how about...
  85. 85. <soapenv:Body> <registerUserResponse soapenv:encodingStyle=" g/"> <registerUserReturn xsi:type="xsd:string"> &lt;error code=&quot;266&quot; &gt;Incorrect login&lt;/error&gt; </registerUserReturn></registerUserResponse></soapenv:Body> • Incorrect password • Incorrect first name • Group with name null doesn't exist • Group with name admin doesn't exist • Group with name Administrator doesn't exist • And how about „root”? RegisterUser
  86. 86. Game Over <soapenv:Body> <registerUserResponse soapenv:encodingStyle=" encoding/"> <registerUserReturn xsi:type="xsd:string"> User was registered sucessfully with id=5392745 Access to system with administartor rights. Possible to manage accounts of all other users.
  87. 87. Architecture
  88. 88. Default HTTP error response Reveals used version
  89. 89. Known vulnerabilities based on version
  90. 90. Apache Tomcat < 6.0.20
  91. 91. <role rolename="tomcat"/> <role rolename="role1"/> <user username="tomcat" password="tomcat" roles="tomcat"/> <user username="both" password="tomcat" roles="tomcat,role1"/> <user username="role1" password="tomcat" roles="role1"/> tomcat-users.xml
  92. 92. Apache Tomcat Manager
  93. 93. Tomcat worm using weak passwords tryLogins = { "admin:admin", "tomcat:tomcat", "admin:", "tomcat:", "root:root", "manager:manager", "tomcat:admin", "admin:password"};
  94. 94. OWASP dependency check / dependency track Automatically checks for known vulnerabilities in used components
  95. 95. THE TAKEAWAY
  96. 96. • Use secure design architecture • Least privilege principle (default: deny) • Code that enforces good practices • Leverage existing security mechanisms, but be aware of their shortcomings and secure use scenario. • Secure configuration • Keep the components up to date • Change default credentials • Harden the configuration • Leverage additional layers of protection (IDS, WAF) Key takeaways
  97. 97. Our presentations (including this one), resources Free security consultancy service: See also
  98. 98. And for the Happy(?)-End – the pentester’s view Features at low cost compromising on security is just obscene ;) Let’s do it better!
  99. 99. Thank you, looking forward to contact! MORE THAN SECURITY TESTING Free security consultancy service: