dedacota: toward preventing server-side xss via automatic code and data separation

Post on 02-Dec-2014

1.748 Views

Category:

Technology

5 Downloads

Preview:

Click to see full reader

DESCRIPTION

Talk I gave at the ACM Conference on Computer and Communications Security (CCS) 2013 on the paper "deDacota: Toward Preventing Server-Side XSS via Automatic Code and Data Separation" which describes an approach to solving Cross-Site Scripting (XSS) vulnerabilities via applying the security principles of Code and Data separation. The paper is located here: http://cs.ucsb.edu/~adoupe/static/dedacota-ccs2013.pdf

TRANSCRIPT

deDacota: Toward Preventing Server-Side XSS via Automatic

Code and Data SeparationAdam Doupé, Weidong Cui€, Mariusz H. Jakubowski€, Marcus

Peinado€, Christopher Kruegel, and Giovanni Vigna

University of California, Santa Barbara€Microsoft Research

CCS 2013 – 11/7/13

Doupé - 11/7/13

XSS Vulnerabilities Still Exist Today

Doupé - 11/7/13

Doupé - 11/7/13Courtesy of Ashar Javed

Doupé - 11/7/13

Test.aspx

<html><body><p>Hello <%= this.Name %></p></body>

</html>

Doupé - 11/7/13

http://example.com/Test.aspx?name=adam

Ask Test.dll for output

<html> <body> <p>Hello <%= this.Name %></p> </body></html>

Doupé - 11/7/13

http://example.com/Test.aspx?name=adam

Ask Test.dll for output

<html> <body> <p>Hello adam</p> </body></html>

Doupé - 11/7/13

http://example.com/Test.aspx?name=adam

Ask Test.dll for output

<html> <body> <p>Hello adam</p> </body></html>

Doupé - 11/7/13

http://example.com/Test.aspx?name=adam

Ask Test.dll for output

<html> <body> <p>Hello adam</p> </body></html>

Doupé - 11/7/13

http://example.com/Test.aspx?name=adam

Ask Test.dll for output

<html> <body> <p>Hello adam</p> </body></html>

Doupé - 11/7/13

Test.aspxhttp://example.com/Test.aspx?name=<script>alert("xss");</script>

<html><body>

<p>Hello <%= this.Name %></script></p>

</body></html>

Doupé - 11/7/13

Test.aspxhttp://example.com/Test.aspx?name=<script>alert("xss");</script>

<html><body>

<p>Hello <script>alert("xss"); </script></p>

</body></html>

Doupé - 11/7/13

Test.aspxhttp://example.com/Test.aspx?name=<script>alert("xss");</script>

<html><body>

<p>Hello <script>alert("xss"); </script></p>

</body></html>

Doupé - 11/7/13

XSS – Impact• Steal cookies

• Perform actions as user

• Exploit user’s browser

• Fake login form

Doupé - 11/7/13

Fixing XSS – Sanitization<html>

<body><p>Hello <%= HtmlEncode(this.Name) %></p></body>

</html>

Doupé - 11/7/13

Fixing XSS – Sanitization<html>

<body><p>Hello <%= HtmlEncode(this.Name) %></p></body>

</html>

<script>alert("xss");</script>

&lt;script&gt;alert("xss");&lt;/script&gt;

Doupé - 11/7/13

XSS as Input Validation

Doupé - 11/7/13

XSS as Input ValidationProblem Research

Find All Paths WWW 2004, USENIX 2005, Oakland 2006

Many Different Contexts CCS 2011, CCS 2011

Is Sanitization Correct? Oakland 2008, USENIX 2011

Parsing Quirks Oakland 2009

Doupé - 11/7/13

XSS as Input ValidationProblem Research

Find All Paths WWW 2004, USENIX 2005, Oakland 2006

Different Context CCS 2011, CCS 2011

Is Sanitization Correct? Oakland 2008, USENIX 2011

Parsing Quirks Oakland 2009

Doupé - 11/7/13

XSS as Input ValidationProblem Research

Find All Paths WWW 2004, USENIX 2005, Oakland 2006

Different Context CCS 2011, CCS 2011

Is Sanitization Correct? Oakland 2008, USENIX 2011

Parsing Quirks Oakland 2009

Doupé - 11/7/13

XSS as Input ValidationProblem Research

Find All Paths WWW 2004, USENIX 2005, Oakland 2006

Different Context CCS 2011, CCS 2011

Is Sanitization Correct? Oakland 2008, USENIX 2011

Parsing Quirks Oakland 2009, CCS 2013

Doupé - 11/7/13

XSS as Input ValidationProblem Research

Find All Paths WWW 2004, USENIX 2005, Oakland 2006

Different Context CCS 2011, CCS 2011

Is Sanitization Correct? Oakland 2008, USENIX 2011

Parsing Quirks Oakland 2009, CCS 2013

We want to fundamentally solve XSS vulnerabilities

Doupé - 11/7/13

Another Example<html>

<body><script>

alert("welcome to example.com!");

</script><p>Hello <%= this.Name %></p>

</body></html>

Doupé - 11/7/13

Another Example<html>

<body><script>

alert("welcome to example.com!");

</script><p>Hello <%= this.Name %></p>

</body></html>

Developer indented for this code to be executed on the browser

Doupé - 11/7/13

Another Examplehttp://example.com/Test.aspx?name=<script>alert("xss");</script>

<html><body>

<script>alert("welcome to

example.com!");</script><p>Hello <%= this.Name %>

</p></body>

</html>

Doupé - 11/7/13

Another Examplehttp://example.com/Test.aspx?name=<script>alert("xss");</script>

<html><body>

<script>alert("welcome to

example.com!");</script><p>Hello

<script>alert("xss");</script> </p></body>

</html>

Doupé - 11/7/13

The Fundamental Problemhttp://example.com/Test.aspx?name=<script>alert("xss");</script>

<html><body>

<script>alert("welcome to

example.com!");</script><p>Hello

<script>alert("xss");</script> </p></body>

</html>

Developer indented for this code to be executed on the browser

Developer did not intend for this code to be executed on the browser

Doupé - 11/7/13

The Fundamental Problemhttp://example.com/Test.aspx?name=<script>alert("xss");</script>

<html><body>

<script>alert("welcome to

example.com!");</script><p>Hello

<script>alert("xss");</script> </p></body>

</html>

Developer indented for this code to be executed on the browser

Developer did not intend for this code to be executed on the browser

The browser can’t tell the difference!

Doupé - 11/7/13

The Fundamental Solution

<html> <body> <script> alert("welcome to example.com!"); </script> <p>Hello <%= this.Name %> </p> </body></html>

alert("welcome to example.com!");

CodeData

Doupé - 11/7/13

The Fundamental Solution

<html> <body> <script> alert("welcome to example.com!"); </script> <p>Hello <%= this.Name %> </p> </body></html>

alert("welcome to example.com!");

CodeDataTo fundamentally solve XSS

vulnerabilities, we must apply the basic security principles of Code

and Data separation!

Doupé - 11/7/13

Content Security Policy (CSP)• Mechanism for the website to communicate a policy to the browser about

what JavaScript to execute• The browser then enforces this policy• Supported by many modern browsers (68% of users use one of these

browsers – Firefox– Chrome– IE (10)– Safari– Opera– iOS– Android

Doupé - 11/7/13

Content Security Policy

<html> <body> <script> alert("welcome to example.com!"); </script> <p>Hello <%= this.Name %> </p> </body></html>

alert("welcome to example.com!");

Code

DataContent-Security-Policy: script-src http://example.com/0cc111eb135.js

Doupé - 11/7/13

Content Security Policy

<html> <body> <script src="0cc111eb135.js"> </script> <p>Hello <%= this.Name %> </p> </body></html>

alert("welcome to example.com!");

Code

DataContent-Security-Policy: script-src http://example.com/0cc111eb135.js

Doupé - 11/7/13

Code and Data Separation• Code and Data separation from start

– No legacy applications

• Manually rewrite application– Difficult and error-prone (HotSec 2011)

deDacota: Automatically separate code and data of a web application

Doupé - 11/7/13

Threat Model• Benign web application

– The developer has not obfuscated the web application

• Server-side XSS– Our approach will only address traditional XSS, in other words,

XSS where the resulting bug is in the server-side code

• Inline JavaScript – For the deDacota prototype, we focused only on inline JavaScript– We ignore JavaScript in HTML attributes and CSS

Doupé - 11/7/13

DESIGN

Doupé - 11/7/13

deDacota Process

Approximate HTML Output

Extract Inline JavaScript

Rewrite Web Application

Doupé - 11/7/13

deDacota Process

Approximate HTML Output

Extract Inline JavaScript

Rewrite Web Application

The goal is to rewrite the web application so that it is

semantically equivalent yet separates the code and data.

Doupé - 11/7/13

Approximate HTML Output<%@ Page Language="C#" CodeBehind="CodeBehind.cs" Inherits="Test" %>

<html><body>

<p>Hello <%= this.Name %></p><%= Scripts() %>

</body></html>

Doupé - 11/7/13

Approximate HTML Outputclass test_aspx : System.Web.UI.Page {

public test_aspx () {this.Name = Request.QueryString["name"];this.Year = "2013";}protected void Render(HtmlTextWriter writer) {writer.write("<html><body><p>");writer.write(this.Name);writer.write(Scripts());writer.write("</p></body></html>");}protected string Scripts() {return "<script>alert('" + this.Year + "');</script>";}

}

Doupé - 11/7/13

Approximate HTML Outputclass test_aspx : System.Web.UI.Page {

public test_aspx () {this.Name = Request.QueryString["name"];this.Year = "2013";}protected void Render(HtmlTextWriter writer) {writer.write("<html><body><p>");writer.write(this.Name);writer.write(Scripts());writer.write("</p></body></html>");}protected string Scripts() {return "<script>alert('" + this.Year + "');</script>";}

}

The goal here is to create a graph that approximates the HTML

content of the web page. We use static analysis techniques to

construct the graph.

Doupé - 11/7/13

Approximate HTML Outputclass test_aspx : System.Web.UI.Page {

public test_aspx () {this.Name = Request.QueryString["name"];this.Year = "2013";}protected void Render(HtmlTextWriter writer) {writer.write("<html><body><p>");writer.write(this.Name);writer.write(Scripts());writer.write("</p></body></html>");}protected string Scripts() {return "<script>alert('" + this.Year + "');</script>";}

}

"<html><body><p>"

Doupé - 11/7/13

Approximate HTML Outputclass test_aspx : System.Web.UI.Page {

public test_aspx () {this.Name = Request.QueryString["name"];this.Year = "2013";}protected void Render(HtmlTextWriter writer) {writer.write("<html><body><p>");writer.write(this.Name);writer.write(Scripts());writer.write("</p></body></html>");}protected string Scripts() {return "<script>alert('" + this.Year + "');</script>";}

}

this.Name

"<html><body><p>"

Doupé - 11/7/13

Approximate HTML Outputclass test_aspx : System.Web.UI.Page {

public test_aspx () {this.Name = Request.QueryString["name"];this.Year = "2013";}protected void Render(HtmlTextWriter writer) {writer.write("<html><body><p>");writer.write(this.Name);writer.write(Scripts());writer.write("</p></body></html>");}protected string Scripts() {return "<script>alert('" + this.Year + "');</script>";}

}

this.Name

"<html><body><p>"

Doupé - 11/7/13

Approximate HTML Outputclass test_aspx : System.Web.UI.Page {

public test_aspx () {this.Name = Request.QueryString["name"];this.Year = "2013";}protected void Render(HtmlTextWriter writer) {writer.write("<html><body><p>");writer.write(this.Name);writer.write(Scripts());writer.write("</p></body></html>");}protected string Scripts() {return "<script>alert('" + this.Year + "');</script>";}

}

this.Name

"<html><body><p>"

Here we need to analyze the control flow of the application,

which means following the control flow into the Scripts()

method.

Doupé - 11/7/13

Approximate HTML Outputclass test_aspx : System.Web.UI.Page {

public test_aspx () {this.Name = Request.QueryString["name"];this.Year = "2013";}protected void Render(HtmlTextWriter writer) {writer.write("<html><body><p>");writer.write(this.Name);writer.write(Scripts());writer.write("</p></body></html>");}protected string Scripts() {return "<script>alert('" + this.Year + "');</script>";}

}

this.Name

"<html><body><p>"

Doupé - 11/7/13

Approximate HTML Outputclass test_aspx : System.Web.UI.Page {

public test_aspx () {this.Name = Request.QueryString["name"];this.Year = "2013";}protected void Render(HtmlTextWriter writer) {writer.write("<html><body><p>");writer.write(this.Name);writer.write(Scripts());writer.write("</p></body></html>");}protected string Scripts() {return "<script>alert('" + this.Year + "');</script>";}

}

this.Name

"<html><body><p>"

Here we encounter string concatenation, which our analysis

is able to handle.

Doupé - 11/7/13

Approximate HTML Outputclass test_aspx : System.Web.UI.Page {

public test_aspx () {this.Name = Request.QueryString["name"];this.Year = "2013";}protected void Render(HtmlTextWriter writer) {writer.write("<html><body><p>");writer.write(this.Name);writer.write(Scripts());writer.write("</p></body></html>");}protected string Scripts() {return "<script>alert('" + this.Year + "');</script>";}

}

"<script>alert('"

this.Year

this.Name

"<html><body><p>"

"');</script>"

Doupé - 11/7/13

Approximate HTML Outputclass test_aspx : System.Web.UI.Page {

public test_aspx () {this.Name = Request.QueryString["name"];this.Year = "2013";}protected void Render(HtmlTextWriter writer) {writer.write("<html><body><p>");writer.write(this.Name);writer.write(Scripts());writer.write("</p></body></html>");}protected string Scripts() {return "<script>alert('" + this.Year + "');</script>";}

}

"<script>alert('"

this.Year

this.Name

"<html><body><p>"

"');</script>"

Now that we have constructed the approximation graph, we must determine what is being

output by each node in the graph. Here we use data-flow analysis

and points-to analysis.

Doupé - 11/7/13

Approximate HTML Outputclass test_aspx : System.Web.UI.Page {

public test_aspx () {this.Name = Request.QueryString["name"];this.Year = "2013";}protected void Render(HtmlTextWriter writer) {writer.write("<html><body><p>");writer.write(this.Name);writer.write(Scripts());writer.write("</p></body></html>");}protected string Scripts() {return "<script>alert('" + this.Year + "');</script>";}

}

"<script>alert('"

this.Year

this.Name

"<html><body><p>"

"');</script>"

Doupé - 11/7/13

Approximate HTML Outputclass test_aspx : System.Web.UI.Page {

public test_aspx () {this.Name = Request.QueryString["name"];this.Year = "2013";}protected void Render(HtmlTextWriter writer) {writer.write("<html><body><p>");writer.write(this.Name);writer.write(Scripts());writer.write("</p></body></html>");}protected string Scripts() {return "<script>alert('" + this.Year + "');</script>";}

}

"<script>alert('"

this.Year

this.Name

"<html><body><p>"

"');</script>"

<html><body><p>

Doupé - 11/7/13

Approximate HTML Outputclass test_aspx : System.Web.UI.Page {

public test_aspx () {this.Name = Request.QueryString["name"];this.Year = "2013";}protected void Render(HtmlTextWriter writer) {writer.write("<html><body><p>");writer.write(this.Name);writer.write(Scripts());writer.write("</p></body></html>");}protected string Scripts() {return "<script>alert('" + this.Year + "');</script>";}

}

"<script>alert('"

this.Year

this.Name

"<html><body><p>"

"');</script>"

<html><body><p>

In this case, Request.QueryString["name"]is statically undecidable

because it comes from user input. In the approximation graph we

represent this as a * which means the output at this node could be

anything.

Doupé - 11/7/13

Approximate HTML Outputclass test_aspx : System.Web.UI.Page {

public test_aspx () {this.Name = Request.QueryString["name"];this.Year = "2013";}protected void Render(HtmlTextWriter writer) {writer.write("<html><body><p>");writer.write(this.Name);writer.write(Scripts());writer.write("</p></body></html>");}protected string Scripts() {return "<script>alert('" + this.Year + "');</script>";}

}

"<script>alert('"

this.Year

this.Name

"<html><body><p>"

"');</script>"

<html><body><p>

*

Doupé - 11/7/13

Approximate HTML Outputclass test_aspx : System.Web.UI.Page {

public test_aspx () {this.Name = Request.QueryString["name"];this.Year = "2013";}protected void Render(HtmlTextWriter writer) {writer.write("<html><body><p>");writer.write(this.Name);writer.write(Scripts());writer.write("</p></body></html>");}protected string Scripts() {return "<script>alert('" + this.Year + "');</script>";}

}

"<script>alert('"

this.Year

this.Name

"<html><body><p>"

"');</script>"

<html><body><p>

*

<script>alert('

Doupé - 11/7/13

Approximate HTML Outputclass test_aspx : System.Web.UI.Page {

public test_aspx () {this.Name = Request.QueryString["name"];this.Year = "2013";}protected void Render(HtmlTextWriter writer) {writer.write("<html><body><p>");writer.write(this.Name);writer.write(Scripts());writer.write("</p></body></html>");}protected string Scripts() {return "<script>alert('" + this.Year + "');</script>";}

}

"<script>alert('"

this.Year

this.Name

"<html><body><p>"

"');</script>"

<html><body><p>

*

<script>alert('

2013

Doupé - 11/7/13

Approximate HTML Outputclass test_aspx : System.Web.UI.Page {

public test_aspx () {this.Name = Request.QueryString["name"];this.Year = "2013";}protected void Render(HtmlTextWriter writer) {writer.write("<html><body><p>");writer.write(this.Name);writer.write(Scripts());writer.write("</p></body></html>");}protected string Scripts() {return "<script>alert('" + this.Year + "');</script>";}

}

"<script>alert('"

this.Year

this.Name

"<html><body><p>"

"');</script>"

<html><body><p>

*

<script>alert('

2013

');</script>

Doupé - 11/7/13

<html><body><p>

*

<script>alert('

2013

');</script>

</p></body></html>

Doupé - 11/7/13

<html><body><p>

*

<script>alert('

2013

');</script>

</p></body></html>

This approximation graph contains a static approximation of

the HTML content of the web page. Any path through this

graph is one possible output of the page.

Doupé - 11/7/13

In this example approximation graph from a real-world application, the branch in the graph comes from a

conditional branch in the control-flow of the application.

Doupé - 11/7/13

Statically undecidable content, represented here as a *, can come from two different areas:

1. Statically undecidable according to the static analysis.2. To make our analysis conservative, we treat all loops as

outputting a *, because we cannot statically determine how many times a loop will execute.

Doupé - 11/7/13

Extract Inline JavaScript

Doupé - 11/7/13

In the second step, we simply extract the inline JavaScript (aka the developer intended code) from the approximation

graph.

Doupé - 11/7/13

Rewrite Web Application

<html> <body> <script> alert("welcome to example.com!"); </script> <p>Hello <%= this.Name %> </p> </body></html>

alert("welcome to example.com!");

Doupé - 11/7/13

Rewrite Web Application

<html> <body> <script src="0cc111eb135.js"> </script> <p>Hello <%= this.Name %> </p> </body></html>

alert("welcome to example.com!");

Code

DataContent-Security-Policy: script-src http://example.com/0cc111eb135.js

Doupé - 11/7/13

Rewrite Web Application

<html> <body> <script src="0cc111eb135.js"> </script> <p>Hello <%= this.Name %> </p> </body></html>

alert("welcome to example.com!");

Code

DataContent-Security-Policy: script-src http://example.com/0cc111eb135.js

At this point, if the inline JavaScript code is static, we have

protected the application. No attacked data in the Data

segment will ever be interpreted as Code.

Doupé - 11/7/13

Rewrite Web Application

<html> <body> <script src="0cc111eb135.js"> </script> <p>Hello <%= this.Name %> </p> </body></html>

alert("welcome to example.com!");

Code

DataContent-Security-Policy: script-src http://example.com/0cc111eb135.js

Unfortunately, developers sometimes dynamically generate the Code of an application. If this

happens with untrusted Data, there can still be a XSS

vulnerability.

Doupé - 11/7/13

Dynamic Inline JavaScript

<html> <script> var username = "<%= Username %>"; </script></html>

Doupé - 11/7/13

Dynamic Inline JavaScript

<html> <script> var username = "<%= Username %>"; </script></html>

var username = "<%= Username %>";

CodeData

Here, the developer has chosen to dynamically generate the Code from untrusted data.

Doupé - 11/7/13

Dynamic Inline JavaScript

<html> <script> var username = "<%= Username %>"; </script></html>

var username = "<%= Username %>";

CodeData

var username = "*";

Doupé - 11/7/13

Dynamic Inline JavaScript

<html> <script> var username = "<%= Username %>"; </script></html>

var username = "<%= Username %>";

CodeData

var username = "*";

We developed a technique to safely transform cases of dynamic inline

JavaScript. If the statically undecidable content is used in a known JavaScript

context (JavaScript string or comment), we can safely rewrite the application. We call these cases “safe dynamic

inline JavaScript.”

Doupé - 11/7/13

EVALUATION

Doupé - 11/7/13

ApplicationsApplication Lines of Code Known

Vulnerability

BugTracker.NET 35,674 CVE-2010-3266

BlogEngine.NET 29,512 CVE-2008-6476

BlogSA.NET 6,994 CVE-2009-0814

ScrewTurn Wiki 12,155 CVE-2008-3483

WebGoat.NET 11,993 2 Intentional

ChronoZoom 21,261 N/A

Doupé - 11/7/13

Evaluation• Security

– Crafted exploits for applications with known vulnerabilities– Transformed applications, along with CSP, blocked the

exploits

• Functional correctness– ChronoZoom had 160 JavaScript tests and all passed

after the transformation– Manually browsed the application and source code

looking for missing inline JavaScript

Doupé - 11/7/13

BugTracker.NET BlogEngine.NET BlogSA.NET ScrewTurn Wiki WebGoat.NET ChronoZoom0%

10%

20%

30%

40%

50%

60%

70%

80%

90%

100%

Unsafe DynamicSafe DynamicStatic

Doupé - 11/7/13

BugTracker.NET BlogEngine.NET BlogSA.NET ScrewTurn Wiki WebGoat.NET ChronoZoom0%

10%

20%

30%

40%

50%

60%

70%

80%

90%

100%

Unsafe DynamicSafe DynamicStatic

Here we are going to look at what percentage of the inline

JavaScript in each application is either: static, safe dynamic, or

unsafe dynamic.

Doupé - 11/7/13

BugTracker.NET BlogEngine.NET BlogSA.NET ScrewTurn Wiki WebGoat.NET ChronoZoom0%

10%

20%

30%

40%

50%

60%

70%

80%

90%

100%

41

4

10 27

6 5Unsafe DynamicSafe DynamicStatic

Doupé - 11/7/13

BugTracker.NET BlogEngine.NET BlogSA.NET ScrewTurn Wiki WebGoat.NET ChronoZoom0%

10%

20%

30%

40%

50%

60%

70%

80%

90%

100%

41

4

10 27

6 5

3

10

14

Unsafe DynamicSafe DynamicStatic

Doupé - 11/7/13

BugTracker.NET BlogEngine.NET BlogSA.NET ScrewTurn Wiki WebGoat.NET ChronoZoom0%

10%

20%

30%

40%

50%

60%

70%

80%

90%

100%

41

4

10 27

6 5

3

10

14

Unsafe DynamicSafe DynamicStatic

In these safe dynamic situations, we are able to safely transform the dynamic inline JavaScript code.

Doupé - 11/7/13

BugTracker.NET BlogEngine.NET BlogSA.NET ScrewTurn Wiki WebGoat.NET ChronoZoom0%

10%

20%

30%

40%

50%

60%

70%

80%

90%

100%

41

4

10 27

6 5

3

10

14

2

41 4

Unsafe DynamicSafe DynamicStatic

Doupé - 11/7/13

BugTracker.NET BlogEngine.NET BlogSA.NET ScrewTurn Wiki WebGoat.NET ChronoZoom0%

10%

20%

30%

40%

50%

60%

70%

80%

90%

100%

41

4

10 27

6 5

3

10

14

2

41 4

Unsafe DynamicSafe DynamicStaticIn cases of unsafe dynamic inline JavaScript, we alert the

developer that the transformation could potentially contain an XSS vulnerability. After the developer confirms the

absence of an XSS vulnerability in the unsafe dynamic inline JavaScript, then the application is guaranteed free of

XSS vulnerabilities.

Doupé - 11/7/13

Limitations• Might miss inline JavaScript

– Loops– Dynamic code execution

• Does not handle HTML attributes and CSS

Doupé - 11/7/13

Summary

• Code and Data separation necessary to

prevent XSS

• deDacota can automatically separate

Code and Data of web application

• deDacota works in practice

Doupé - 11/7/13

DEDACOTA: TOWARD PREVENTING SERVER-SIDE XSS VIA AUTOMATIC CODE AND DATA SEPARATION

Adam Doupé

Email: adoupe@cs.ucsb.eduTwitter: @adamdoupe

top related