Skip to content

Commit 5143c7c

Browse files
authored
Merge pull request ESAPI#393 from xeno6696/develop
Issue 316 -- updated code to account for httpOnly and Secure cookie …
2 parents 4853956 + 2837538 commit 5143c7c

File tree

11 files changed

+408
-294
lines changed

11 files changed

+408
-294
lines changed

pom.xml

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,7 @@
309309
<plugin>
310310
<groupId>org.codehaus.mojo</groupId>
311311
<artifactId>findbugs-maven-plugin</artifactId>
312-
<version>2.5.5</version>
312+
<version>3.0.4</version>
313313
<configuration>
314314
<findbugsXmlOutput>true</findbugsXmlOutput>
315315
<findbugsXmlWithMessages>true</findbugsXmlWithMessages>
@@ -412,6 +412,39 @@
412412
</reporting>
413413

414414
<profiles>
415+
<profile>
416+
<id>doclint-java8-disable</id>
417+
<activation>
418+
<jdk>[1.8,)</jdk>
419+
</activation>
420+
<build>
421+
<plugins>
422+
<plugin>
423+
<groupId>org.apache.maven.plugins</groupId>
424+
<artifactId>maven-javadoc-plugin</artifactId>
425+
<configuration>
426+
<additionalparam>-Xdoclint:none</additionalparam>
427+
</configuration>
428+
</plugin>
429+
<plugin>
430+
<groupId>org.apache.maven.plugins</groupId>
431+
<artifactId>maven-site-plugin</artifactId>
432+
<version>3.4</version>
433+
<configuration>
434+
<reportPlugins>
435+
<plugin>
436+
<groupId>org.apache.maven.plugins</groupId>
437+
<artifactId>maven-javadoc-plugin</artifactId>
438+
<configuration>
439+
<additionalparam>-Xdoclint:none</additionalparam>
440+
</configuration>
441+
</plugin>
442+
</reportPlugins>
443+
</configuration>
444+
</plugin>
445+
</plugins>
446+
</build>
447+
</profile>
415448
<profile>
416449
<!-- Activate to sign jars and build distributable download. -->
417450
<id>dist</id>

src/main/java/org/owasp/esapi/Encoder.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package org.owasp.esapi;
1717

1818
import java.io.IOException;
19+
import java.net.URI;
1920

2021
import org.owasp.esapi.codecs.Codec;
2122
import org.owasp.esapi.errors.EncodingException;
@@ -525,4 +526,15 @@ public interface Encoder {
525526
*/
526527
byte[] decodeFromBase64(String input) throws IOException;
527528

529+
/**
530+
*
531+
* Get a version of the input URI that will be safe to run regex and other validations against.
532+
* It is not recommended to persist this value as it will transform user input. This method
533+
* will not test to see if the URI is RFC-3986 compliant.
534+
*
535+
* @param input
536+
* @return
537+
*/
538+
public String getCanonicalizedURI(URI dirtyUri);
539+
528540
}

src/main/java/org/owasp/esapi/Validator.java

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -708,17 +708,6 @@ public interface Validator {
708708
*/
709709
boolean isValidURI(String context, String input, boolean allowNull);
710710

711-
/**
712-
*
713-
* Get a version of the input URI that will be safe to run regex and other validations against.
714-
* It is not recommended to persist this value as it will transform user input. This method
715-
* will not test to see if the URI is RFC-3986 compliant.
716-
*
717-
* @param input
718-
* @return
719-
*/
720-
public String getCanonicalizedURI(URI dirtyUri);
721-
722711
/**
723712
* Will return a {@code URI} object that will represent a fully parsed and legal URI
724713
* as specified in RFC-3986.

src/main/java/org/owasp/esapi/reference/DefaultEncoder.java

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,23 @@
1717

1818
import java.io.IOException;
1919
import java.io.UnsupportedEncodingException;
20+
import java.net.URI;
2021
import java.net.URLDecoder;
2122
import java.net.URLEncoder;
2223
import java.util.ArrayList;
24+
import java.util.EnumMap;
2325
import java.util.Iterator;
26+
import java.util.LinkedHashMap;
27+
import java.util.LinkedList;
2428
import java.util.List;
29+
import java.util.Map;
30+
import java.util.Map.Entry;
31+
import java.util.Set;
2532

2633
import org.owasp.esapi.ESAPI;
2734
import org.owasp.esapi.Encoder;
2835
import org.owasp.esapi.Logger;
36+
import org.owasp.esapi.SecurityConfiguration;
2937
import org.owasp.esapi.codecs.Base64;
3038
import org.owasp.esapi.codecs.CSSCodec;
3139
import org.owasp.esapi.codecs.Codec;
@@ -452,4 +460,150 @@ public byte[] decodeFromBase64(String input) throws IOException {
452460
}
453461
return Base64.decode( input );
454462
}
463+
464+
/**
465+
* {@inheritDoc}
466+
*
467+
* This will extract each piece of a URI according to parse zone as specified in <a href="https://www.ietf.org/rfc/rfc3986.txt">RFC-3986</a> section 3,
468+
* and it will construct a canonicalized String representing a version of the URI that is safe to
469+
* run regex against.
470+
*
471+
* @param dirtyUri
472+
* @return Canonicalized URI string.
473+
* @throws IntrusionException
474+
*/
475+
public String getCanonicalizedURI(URI dirtyUri) throws IntrusionException{
476+
477+
// From RFC-3986 section 3
478+
// URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
479+
//
480+
// hier-part = "//" authority path-abempty
481+
// / path-absolute
482+
// / path-rootless
483+
// / path-empty
484+
485+
// The following are two example URIs and their component parts:
486+
//
487+
// foo://example.com:8042/over/there?name=ferret#nose
488+
// \_/ \______________/\_________/ \_________/ \__/
489+
// | | | | |
490+
// scheme authority path query fragment
491+
// | _____________________|__
492+
// / \ / \
493+
// urn:example:animal:ferret:nose
494+
Map<UriSegment, String> parseMap = new EnumMap<UriSegment, String>(UriSegment.class);
495+
parseMap.put(UriSegment.SCHEME, dirtyUri.getScheme());
496+
//authority = [ userinfo "@" ] host [ ":" port ]
497+
parseMap.put(UriSegment.AUTHORITY, dirtyUri.getRawAuthority());
498+
parseMap.put(UriSegment.SCHEMSPECIFICPART, dirtyUri.getRawSchemeSpecificPart());
499+
parseMap.put(UriSegment.HOST, dirtyUri.getHost());
500+
//if port is undefined, it will return -1
501+
Integer port = new Integer(dirtyUri.getPort());
502+
parseMap.put(UriSegment.PORT, port == -1 ? "": port.toString());
503+
parseMap.put(UriSegment.PATH, dirtyUri.getRawPath());
504+
parseMap.put(UriSegment.QUERY, dirtyUri.getRawQuery());
505+
parseMap.put(UriSegment.FRAGMENT, dirtyUri.getRawFragment());
506+
507+
//Now we canonicalize each part and build our string.
508+
StringBuilder sb = new StringBuilder();
509+
510+
//Replace all the items in the map with canonicalized versions.
511+
512+
Set<UriSegment> set = parseMap.keySet();
513+
514+
SecurityConfiguration sg = ESAPI.securityConfiguration();
515+
boolean allowMixed = sg.getBooleanProp("Encoder.AllowMixedEncoding");
516+
boolean allowMultiple = sg.getBooleanProp("Encoder.AllowMultipleEncoding");
517+
for(UriSegment seg: set){
518+
String value = canonicalize(parseMap.get(seg), allowMultiple, allowMixed);
519+
value = value == null ? "" : value;
520+
//In the case of a uri query, we need to break up and canonicalize the internal parts of the query.
521+
if(seg == UriSegment.QUERY && null != parseMap.get(seg)){
522+
StringBuilder qBuilder = new StringBuilder();
523+
try {
524+
Map<String, List<String>> canonicalizedMap = this.splitQuery(dirtyUri);
525+
Set<Entry<String, List<String>>> query = canonicalizedMap.entrySet();
526+
Iterator<Entry<String, List<String>>> i = query.iterator();
527+
while(i.hasNext()){
528+
Entry<String, List<String>> e = i.next();
529+
String key = (String) e.getKey();
530+
String qVal = "";
531+
List<String> list = (List<String>) e.getValue();
532+
if(!list.isEmpty()){
533+
qVal = list.get(0);
534+
}
535+
qBuilder.append(key)
536+
.append("=")
537+
.append(qVal);
538+
539+
if(i.hasNext()){
540+
qBuilder.append("&");
541+
}
542+
}
543+
value = qBuilder.toString();
544+
} catch (UnsupportedEncodingException e) {
545+
logger.debug(Logger.EVENT_FAILURE, "decoding error when parsing [" + dirtyUri.toString() + "]");
546+
}
547+
}
548+
//Check if the port is -1, if it is, omit it from the output.
549+
if(seg == UriSegment.PORT){
550+
if("-1" == parseMap.get(seg)){
551+
value = "";
552+
}
553+
}
554+
parseMap.put(seg, value );
555+
}
556+
557+
return buildUrl(parseMap);
558+
}
559+
560+
/**
561+
* All the parts should be canonicalized by this point. This is straightforward assembly.
562+
*
563+
* @param set
564+
* @return
565+
*/
566+
protected String buildUrl(Map<UriSegment, String> parseMap){
567+
StringBuilder sb = new StringBuilder();
568+
sb.append(parseMap.get(UriSegment.SCHEME))
569+
.append("://")
570+
//can't use SCHEMESPECIFICPART for this, because we need to canonicalize all the parts of the query.
571+
//USERINFO is also deprecated. So we technically have more than we need.
572+
.append(parseMap.get(UriSegment.AUTHORITY) == null || parseMap.get(UriSegment.AUTHORITY).equals("") ? "" : parseMap.get(UriSegment.AUTHORITY))
573+
.append(parseMap.get(UriSegment.PATH) == null || parseMap.get(UriSegment.PATH).equals("") ? "" : parseMap.get(UriSegment.PATH))
574+
.append(parseMap.get(UriSegment.QUERY) == null || parseMap.get(UriSegment.QUERY).equals("")
575+
? "" : "?" + parseMap.get(UriSegment.QUERY))
576+
.append((parseMap.get(UriSegment.FRAGMENT) == null) || parseMap.get(UriSegment.FRAGMENT).equals("")
577+
? "": "#" + parseMap.get(UriSegment.FRAGMENT))
578+
;
579+
return sb.toString();
580+
}
581+
582+
public enum UriSegment {
583+
AUTHORITY, SCHEME, SCHEMSPECIFICPART, USERINFO, HOST, PORT, PATH, QUERY, FRAGMENT
584+
}
585+
586+
587+
/**
588+
* The meat of this method was taken from StackOverflow: http://stackoverflow.com/a/13592567/557153
589+
* It has been modified to return a canonicalized key and value pairing.
590+
*
591+
* @param java URI
592+
* @return a map of canonicalized query parameters.
593+
* @throws UnsupportedEncodingException
594+
*/
595+
public Map<String, List<String>> splitQuery(URI uri) throws UnsupportedEncodingException {
596+
final Map<String, List<String>> query_pairs = new LinkedHashMap<String, List<String>>();
597+
final String[] pairs = uri.getQuery().split("&");
598+
for (String pair : pairs) {
599+
final int idx = pair.indexOf("=");
600+
final String key = idx > 0 ? canonicalize(pair.substring(0, idx)) : pair;
601+
if (!query_pairs.containsKey(key)) {
602+
query_pairs.put(key, new LinkedList<String>());
603+
}
604+
final String value = idx > 0 && pair.length() > idx + 1 ? URLDecoder.decode(pair.substring(idx + 1), "UTF-8") : null;
605+
query_pairs.get(key).add(canonicalize(value));
606+
}
607+
return query_pairs;
608+
}
455609
}

src/main/java/org/owasp/esapi/reference/DefaultHTTPUtilities.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import org.owasp.esapi.ESAPI;
4242
import org.owasp.esapi.HTTPUtilities;
4343
import org.owasp.esapi.Logger;
44+
import org.owasp.esapi.SecurityConfiguration;
4445
import org.owasp.esapi.StringUtilities;
4546
import org.owasp.esapi.User;
4647
import org.owasp.esapi.ValidationErrorList;
@@ -931,6 +932,9 @@ public String setRememberToken( HttpServletRequest request, HttpServletResponse
931932
String clearToken = user.getAccountName() + "|" + password;
932933
long expiry = ESAPI.encryptor().getRelativeTimeStamp(maxAge * 1000);
933934
String cryptToken = ESAPI.encryptor().seal(clearToken, expiry);
935+
SecurityConfiguration sg = ESAPI.securityConfiguration();
936+
boolean forceSecureCookies = sg.getBooleanProp("HttpUtilities.ForceSecureCookies");
937+
boolean forceHttpOnly = sg.getBooleanProp("HttpUtilities.ForceHttpOnlyCookies");
934938

935939
// Do NOT URLEncode cryptToken before creating cookie. See Google Issue # 144,
936940
// which was marked as "WontFix".
@@ -939,6 +943,8 @@ public String setRememberToken( HttpServletRequest request, HttpServletResponse
939943
cookie.setMaxAge( maxAge );
940944
cookie.setDomain( domain );
941945
cookie.setPath( path );
946+
cookie.setHttpOnly(forceHttpOnly);
947+
cookie.setSecure(forceSecureCookies);
942948
response.addCookie( cookie );
943949
logger.info(Logger.SECURITY_SUCCESS, "Enabled remember me token for " + user.getAccountName() );
944950
return cryptToken;
@@ -959,14 +965,18 @@ public String setRememberToken(HttpServletRequest request, HttpServletResponse r
959965
String clearToken = user.getAccountName();
960966
long expiry = ESAPI.encryptor().getRelativeTimeStamp(maxAge * 1000);
961967
String cryptToken = ESAPI.encryptor().seal(clearToken, expiry);
962-
968+
SecurityConfiguration sg = ESAPI.securityConfiguration();
969+
boolean forceSecureCookies = sg.getBooleanProp("HttpUtilities.ForceSecureCookies");
970+
boolean forceHttpOnly = sg.getBooleanProp("HttpUtilities.ForceHttpOnlyCookies");
963971
// Do NOT URLEncode cryptToken before creating cookie. See Google Issue # 144,
964972
// which was marked as "WontFix".
965973

966974
Cookie cookie = new Cookie( REMEMBER_TOKEN_COOKIE_NAME, cryptToken );
967975
cookie.setMaxAge( maxAge );
968976
cookie.setDomain( domain );
969977
cookie.setPath( path );
978+
cookie.setHttpOnly(forceHttpOnly);
979+
cookie.setSecure(forceSecureCookies);
970980
response.addCookie( cookie );
971981
logger.info(Logger.SECURITY_SUCCESS, "Enabled remember me token for " + user.getAccountName() );
972982
} catch( IntegrityException e){

0 commit comments

Comments
 (0)