├── exercises ├── 10 │ ├── problem.md │ └── server.js ├── 11 │ ├── problem.md │ └── server.js ├── 12 │ ├── problem.md │ └── server.js ├── 13 │ └── problem.md ├── 14 │ ├── problem.md │ └── server.js ├── 15 │ ├── problem.md │ └── server.js ├── 16 │ ├── problem.md │ └── server.js ├── 17 │ ├── server.js │ └── problem.md ├── 18 │ └── problem.md ├── common │ ├── foot.ejs │ ├── static │ │ ├── bg-stars.gif │ │ ├── cornelly.png │ │ ├── hrvrdio.png │ │ ├── stanfoogle.gif │ │ ├── caloogle.js │ │ └── caloogle.css │ ├── caloogle-logo.ejs │ ├── caloogle-home-page.ejs │ ├── caloogle-search-page.ejs │ ├── head.ejs │ ├── head3.ejs │ ├── head2.ejs │ ├── caloogle-search-page4.ejs │ ├── server.js │ ├── caloogle-search-page5.ejs │ ├── caloogle-search-page2.ejs │ ├── caloogle-search-page3.ejs │ ├── caloogle-guestbook-page.ejs │ └── caloogle.js ├── 05 │ ├── problem.md │ └── server.js ├── 07 │ ├── problem.md │ └── server.js ├── 04 │ ├── problem.md │ └── server.js ├── 03 │ ├── problem.md │ └── server.js ├── 02 │ ├── problem.md │ └── server.js ├── 06 │ ├── problem.md │ └── server.js ├── 09 │ └── problem.md ├── 08 │ ├── problem.md │ └── server.js ├── exercises.json ├── 01 │ ├── server.js │ └── problem.md └── 00 │ └── problem.md ├── static ├── home.png ├── newtab.png ├── workshop.css ├── common.js └── common.css ├── README.md ├── src ├── SURVEY.md └── SOLUTIONS.md ├── test └── basic.js ├── .gitignore ├── server ├── layout.ejs └── index.js └── package.json /exercises/common/foot.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /static/home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stanford-web-security/assign1/HEAD/static/home.png -------------------------------------------------------------------------------- /static/newtab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stanford-web-security/assign1/HEAD/static/newtab.png -------------------------------------------------------------------------------- /exercises/common/static/bg-stars.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stanford-web-security/assign1/HEAD/exercises/common/static/bg-stars.gif -------------------------------------------------------------------------------- /exercises/common/static/cornelly.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stanford-web-security/assign1/HEAD/exercises/common/static/cornelly.png -------------------------------------------------------------------------------- /exercises/common/static/hrvrdio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stanford-web-security/assign1/HEAD/exercises/common/static/hrvrdio.png -------------------------------------------------------------------------------- /exercises/common/static/stanfoogle.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stanford-web-security/assign1/HEAD/exercises/common/static/stanfoogle.gif -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CS 253 Assignment 1 – Journey to the Dark Side 🌘 2 | 3 | Assignment instructions: https://web.stanford.edu/class/cs253/assign1 4 | -------------------------------------------------------------------------------- /exercises/common/static/caloogle.js: -------------------------------------------------------------------------------- 1 | window.sendAnalytics = (...args) => { 2 | console.log(`Sending ${args} to analytics service...`) 3 | } 4 | -------------------------------------------------------------------------------- /exercises/common/caloogle-logo.ejs: -------------------------------------------------------------------------------- 1 | Caloogle 2 | -------------------------------------------------------------------------------- /exercises/common/caloogle-home-page.ejs: -------------------------------------------------------------------------------- 1 | <%- include('head') -%> 2 | 3 |
4 |

5 | <%- include('caloogle-logo') %> 6 |

7 | 8 |
9 | 10 |
11 | 12 |
13 |
14 | 15 | <%- include('foot') -%> 16 | -------------------------------------------------------------------------------- /src/SURVEY.md: -------------------------------------------------------------------------------- 1 | # Assignment 1 Survey Questions (3 points) 2 | 3 | ## Roughly how long did you spend on this assignment? 4 | 5 | TODO: Replace this with your response 6 | 7 | ## What was your favorite part of this assignment and why? 8 | 9 | TODO: Replace this with your response 10 | 11 | ## What was your least favorite part of this assignment and why? 12 | 13 | TODO: Replace this with your response 14 | 15 | ## Any other feedback for this assignment? (optional) 16 | -------------------------------------------------------------------------------- /exercises/18/problem.md: -------------------------------------------------------------------------------- 1 | Congrats, you're all done! ✨ 2 | 3 | Now, go take [CS 181](http://cs181.stanford.edu) and reflect on the ethical implications of all your actions thus far! 😁 4 | 5 | ## Survey 6 | 7 | Your feedback matters a lot! This is a brand new course, so please help us improve by answering the survey questions in `src/SURVEY.md`. As a reward, enjoy some easy points! 8 | 9 | Click this link to call success() and complete this exercise. 10 | 11 | -------------------------------------------------------------------------------- /exercises/common/caloogle-search-page.ejs: -------------------------------------------------------------------------------- 1 | <%- include('head') -%> 2 | 3 |
4 |
5 | 10 | 11 |
12 | 13 | 14 |
15 |
16 | 17 |

Results for <%- q %>:

18 | 19 |
20 | <% results.forEach(result => { %> 21 |

22 | <%= result.title %> 23 | <%= result.url %> 24 |

25 | <% }) %> 26 |
27 |
28 | 29 | <%- include('foot') -%> 30 | -------------------------------------------------------------------------------- /exercises/common/head.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 22 |
23 | -------------------------------------------------------------------------------- /exercises/common/head3.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
20 | 21 | 22 |
23 | -------------------------------------------------------------------------------- /exercises/common/head2.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | > 14 | 15 |
20 | 21 | 22 |
23 | -------------------------------------------------------------------------------- /exercises/05/problem.md: -------------------------------------------------------------------------------- 1 | They're on to you! They saw your last attack in their server logs and have updated their input sanitization code once again. 2 | 3 | ```js 4 | router.get('/search', async (req, res) => { 5 | let q = req.query.q 6 | if (q == null) q = '' 7 | 8 | let oldQ 9 | while (q !== oldQ) { 10 | oldQ = q 11 | q = q.replace(/script|SCRIPT/g, '') 12 | } 13 | 14 | const results = await getResults(q) 15 | res.render('caloogle-search-page', { q, results }) 16 | }) 17 | ``` 18 | 19 | ## Goal 20 | 21 | Can you think of a way to defeat their improved sanitization code and get your ` 39 | 40 | <%- include('foot') -%> 41 | -------------------------------------------------------------------------------- /exercises/06/problem.md: -------------------------------------------------------------------------------- 1 | Your competitor can't believe how successful your attacks have been. They are eager to put an end to this cat-and-mouse game. So, they decide to hire a security consultant to help them fix the problem with their approach once-and-for-all. 2 | 3 | You decide to put your [social engineering](https://en.wikipedia.org/wiki/Social_engineering_(security)) skills to work and see what you can learn about their newly-hired consultant. After a quick phone call, you're able to learn one critical fact about the consultant: they graduated from UC Berkeley. Your fears are assuaged. With renewed confidence, you take another look at their code to find the inevitable bug you know must exist. 4 | 5 | ```js 6 | router.get('/search', async (req, res) => { 7 | let q = req.query.q 8 | if (q == null) q = '' 9 | 10 | let oldQ 11 | while (q !== oldQ) { 12 | oldQ = q 13 | q = q.replace(/script/gi, '') 14 | } 15 | 16 | const results = await getResults(q) 17 | res.render('caloogle-search-page', { q, results }) 18 | }) 19 | ``` 20 | 21 | ## Goal 22 | 23 | Find the XSS vulnerability in the search input field. You should **not** use a ` 15 | ``` 16 | 17 | You suspect that they're using the new foolproof `htmlAttributeEscape()` function to ensure that a user can't "break out" of the quotes in the JavaScript code. After all, the function replaces single quote and double quote characters with their respective HTML entities. 18 | 19 | Is that actually enough? 20 | 21 | ## Goal 22 | 23 | Find the XSS vulnerability in the search input field. You can use any HTML tag to run the `success()` function. 24 | 25 | ## Tip 26 | 27 | You may need to look at the HTML source of the ` 30 | 31 | Before you move on to the next exercise, remember to copy your "attack input" into the `SOLUTIONS.md` file. 32 | -------------------------------------------------------------------------------- /server/layout.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <%= title %> 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 35 | 36 |
37 | <%- content %> 38 |
39 |
40 | 41 | 42 | -------------------------------------------------------------------------------- /exercises/09/problem.md: -------------------------------------------------------------------------------- 1 | Your competitor is fed up with the lackluster results from the UC Berkeley graduate and decide to hire a Stanford student who has completed [CS 253: Web Security](https://cs253.stanford.edu) instead. They are able to quickly implement a foolproof `htmlElementEscape()` function which defeats your shenanigans once and for all. 2 | 3 | Please read the code for `htmlElementEscape()` and ensure you understand how it works in detail. 4 | 5 | ```js 6 | function htmlElementEscape (str) { 7 | return str 8 | // This is not for security, but because '&' is the HTML escape character 9 | // and we don't want the user's input to be treated as an escape sequence. 10 | .replace(/&/g, '&') 11 | 12 | // Without the '<' character, no HTML tags an be created. 13 | .replace(/ { 23 | let q = req.query.q 24 | if (q == null) q = '' 25 | 26 | q = htmlElementEscape(q) 27 | 28 | const results = await getResults(q) 29 | res.render('caloogle-search-page', { q, results }) 30 | }) 31 | ``` 32 | 33 | It seems like we're out of luck for now... 34 | 35 | Click this link to call success() and complete this exercise. 36 | -------------------------------------------------------------------------------- /exercises/08/problem.md: -------------------------------------------------------------------------------- 1 | The security consultant is getting fed up with all these cat-and-mouse games. They realize that if they prevent you from using certain "critical" characters then all your attacks would stop working. 2 | 3 | They also realize that you've been stealing their source code which has made it much easier for you to figure out the weaknesses in their system. So they've shut down that attack vector. You're going to have to rely on your ingenuity to figure out what their code is doing by trying out different inputs and seeing what ends up in the resulting page's HTML. 4 | 5 | ```js 6 | router.get('/search', async (req, res) => { 7 | // TOP SECRET –– REDACTED 8 | }) 9 | ``` 10 | 11 | Note: Do not attempt to look at the server's **server-side source code** to see what the `/search` route handler is doing. This is not in the spirit of this exercise. You can, of course, continue to view the HTML source of the pages that the server sends back to the client. 12 | 13 | ## Goals 14 | 15 | 1. Find the XSS vulnerability in the search input field. You can use any HTML tag you like. 16 | 17 | 1. Write out the code that you believe the server must be executing to process the input. 18 | 19 | ## Tip 20 | 21 | Try submitting various inputs and then look at the HTML source of the ` 24 | 25 | Before you move on to the next exercise, remember to copy your "attack input" as well as your server code into the `SOLUTIONS.md` file. 26 | -------------------------------------------------------------------------------- /exercises/common/caloogle-search-page2.ejs: -------------------------------------------------------------------------------- 1 | <%- include('head') -%> 2 | 3 |
4 |
5 | 10 | 11 |
12 | 13 | 14 |
15 |
16 | 17 |

Results for <%= rawQ %>:

18 | 19 |

Our results are the best! If you don't believe us check out the competition:

20 | 21 | 40 | 41 |
42 | <% results.forEach(result => { %> 43 |

44 | <%= result.title %> 45 | <%= result.url %> 46 |

47 | <% }) %> 48 |
49 |
50 | 51 | <%- include('foot') -%> 52 | -------------------------------------------------------------------------------- /exercises/common/caloogle-search-page3.ejs: -------------------------------------------------------------------------------- 1 | <%- include('head') -%> 2 | 3 |
4 |
5 | 10 | 11 |
12 | 13 | 14 |
15 |
16 | 17 |

Results for <%= rawQ %>:

18 | 19 |

Our results are the best! If you don't believe us check out the competition:

20 | 21 | 40 | 41 |
42 | <% results.forEach(result => { %> 43 |

44 | <%= result.title %> 45 | <%= result.url %> 46 |

47 | <% }) %> 48 |
49 |
50 | 51 | <%- include('foot') -%> 52 | -------------------------------------------------------------------------------- /exercises/exercises.json: -------------------------------------------------------------------------------- 1 | [ 2 | { "heading": true, "name": "Journey to the Dark Side" }, 3 | 4 | { "id": 0, "server": false, "name": "Welcome" }, 5 | 6 | { "heading": true, "name": "Cross Site Scripting into HTML tags" }, 7 | 8 | { "id": 1, "name": "A Truly Disruptive Startup" }, 9 | { "id": 2, "name": "No Script Allowed" }, 10 | { "id": 3, "name": "One More Time, Like You Mean It" }, 11 | { "id": 4, "name": "An Open-and-Shut Case" }, 12 | { "id": 5, "name": "Time to Mix Things Up" }, 13 | { "id": 6, "name": "A Picture is Worth a Thousand Words" }, 14 | { "id": 7, "name": "Between a Rock And a Hard Place" }, 15 | { "id": 8, "name": "Angle of Death" }, 16 | { "id": 9, "server": false, "name": "All in a Day's Work" }, 17 | 18 | { "heading": true, "name": "Cross Site Scripting into HTML Attributes" }, 19 | 20 | { "id": 10, "name": "In the Wrong Place at the Wrong Time" }, 21 | { "id": 11, "name": "You Can't Win 'em All" }, 22 | { "id": 12, "name": "When All is Said and Done" }, 23 | { "id": 13, "server": false, "name": "When You Want a Job Done Right" }, 24 | { "id": 14, "name": "Here Today and Gone Tomorrow" }, 25 | 26 | { "heading": true, "name": "Cross Site Scripting into 60 |
61 | 62 | <%- include('foot') -%> 63 | -------------------------------------------------------------------------------- /exercises/13/problem.md: -------------------------------------------------------------------------------- 1 | Fed up with the lackluster results defending against your attacks, your competitor hires a Stanford student who has completed [CS 253: Web Security](https://cs253.stanford.edu) again. Later, you'll have to have a talk with these students about working for your enemies. 2 | 3 | In the meantime, your competitors is able to implement a foolproof `htmlAttributeEscape()` function which defeats you for now... 😔 4 | 5 | Please read the code for `htmlAttributeEscape()` and ensure you understand how it works in detail. 6 | 7 | ```js 8 | function htmlAttributeEscape (str) { 9 | return str 10 | // This is not for security, but because '&' is the HTML escape character 11 | // and we don't want the user's input to be treated as an escape sequence. 12 | .replace(/&/g, '&') 13 | 14 | // Without the single quote character, the attacker cannot escape from 15 | // inside a single-quoted HTML attribute. 16 | .replace(/'/g, ''') 17 | 18 | // Without the double quote character, the attacker cannot escape from 19 | // inside a double-quoted HTML attribute. 20 | .replace(/"/g, '"') 21 | } 22 | ``` 23 | 24 | Now all your competitor needs to do is call this function whenever they put untrusted data directly into HTML attributes i.e. `
`. 25 | 26 | So, their updated route handler code looks like this now: 27 | 28 | ```js 29 | router.get('/search', async (req, res) => { 30 | let q = req.query.q 31 | if (q == null) q = '' 32 | 33 | q = htmlAttributeEscape(q) 34 | 35 | const results = await getResults(q) 36 | res.render('caloogle-search-page', { q, results }) 37 | }) 38 | ``` 39 | 40 | It seems like we're out of luck for now... 41 | 42 | Click this link to call success() and complete this exercise. 43 | -------------------------------------------------------------------------------- /exercises/10/problem.md: -------------------------------------------------------------------------------- 1 | Your competitor's `htmlElementEscape()` function is working quite well and you haven't been able to attack their users in the wild ever since they deployed it. It's a lot harder to disrupt the status quo like world-changing startup when you can't XSS your competitors. Now, you're beginning to worry that you might not be able to get that next round of funding. This is a huge bummer. 2 | 3 | Fortunately, your competitor just deployed a new version of their site that includes a brand new feature. Perhaps there's a new XSS vulnerability in it? 4 | 5 | The feature is called "Competitor Comparison" and it includes links to competitor search engines so that users can compare search results and decide which search engine is best. Clearly, they feel pretty confident that their results are the best. 6 | 7 | They appear to be using the foolproof `htmlElementEscape()` function written for them by the Stanford CS 253 student to generate the HTML for these links so you think they're guaranteed to be safe: 8 | 9 | ```js 10 | router.get('/search', async (req, res) => { 11 | let q = req.query.q 12 | if (q == null) q = '' 13 | 14 | q = htmlElementEscape(q) 15 | 16 | const results = await getResults(q) 17 | res.render('caloogle-search-page', { q, results }) 18 | }) 19 | ``` 20 | 21 | But upon closer inspection, it appears that they're not using the function correctly. Time to teach them another lesson! 22 | 23 | ## Goal 24 | 25 | Find the XSS vulnerability in the search input field. You can use any HTML you want to run the `success()` function. 26 | 27 | ## Tip 28 | 29 | Try submitting various inputs and then look at the HTML source of the ` 32 | 33 | Before you move on to the next exercise, remember to copy your "attack input" into the `SOLUTIONS.md` file. 34 | -------------------------------------------------------------------------------- /exercises/01/server.js: -------------------------------------------------------------------------------- 1 | /** 2 | * SSSSSSSSSSSSSSS TTTTTTTTTTTTTTTTTTTTTTT OOOOOOOOO PPPPPPPPPPPPPPPPP 3 | * SS:::::::::::::::ST:::::::::::::::::::::T OO:::::::::OO P::::::::::::::::P 4 | * S:::::SSSSSS::::::ST:::::::::::::::::::::T OO:::::::::::::OO P::::::PPPPPP:::::P 5 | * S:::::S SSSSSSST:::::TT:::::::TT:::::TO:::::::OOO:::::::OPP:::::P P:::::P 6 | * S:::::S TTTTTT T:::::T TTTTTTO::::::O O::::::O P::::P P:::::P 7 | * S:::::S T:::::T O:::::O O:::::O P::::P P:::::P 8 | * S::::SSSS T:::::T O:::::O O:::::O P::::PPPPPP:::::P 9 | * SS::::::SSSSS T:::::T O:::::O O:::::O P:::::::::::::PP 10 | * SSS::::::::SS T:::::T O:::::O O:::::O P::::PPPPPPPPP 11 | * SSSSSS::::S T:::::T O:::::O O:::::O P::::P 12 | * S:::::S T:::::T O:::::O O:::::O P::::P 13 | * S:::::S T:::::T O::::::O O::::::O P::::P 14 | * SSSSSSS S:::::S TT:::::::TT O:::::::OOO:::::::OPP::::::PP 15 | * S::::::SSSSSS:::::S T:::::::::T OO:::::::::::::OO P::::::::P 16 | * S:::::::::::::::SS T:::::::::T OO:::::::::OO P::::::::P 17 | * SSSSSSSSSSSSSSS TTTTTTTTTTT OOOOOOOOO PPPPPPPPPP 18 | * 19 | * 20 | * Please don't read this code. Reading this code is not in the spirit of the 21 | * assignment. You'll learn less and you'll also be letting us down. So please 22 | * just attempt the assignment without looking at these files. We trust you to 23 | * act honestly. Thanks! <3 24 | * 25 | * - Feross and the CS 253 course staff 26 | */ 27 | 28 | const { createServer } = require('../common/server') 29 | const { getResults } = require('../common/caloogle') 30 | 31 | const { router } = createServer(4010, __dirname) 32 | 33 | router.get('/', async (req, res) => { 34 | res.render('caloogle-home-page') 35 | }) 36 | 37 | router.get('/search', async (req, res) => { 38 | let q = req.query.q 39 | if (q == null) q = '' 40 | 41 | const results = await getResults(q) 42 | res.render('caloogle-search-page', { q, results }) 43 | }) 44 | -------------------------------------------------------------------------------- /exercises/02/server.js: -------------------------------------------------------------------------------- 1 | /** 2 | * SSSSSSSSSSSSSSS TTTTTTTTTTTTTTTTTTTTTTT OOOOOOOOO PPPPPPPPPPPPPPPPP 3 | * SS:::::::::::::::ST:::::::::::::::::::::T OO:::::::::OO P::::::::::::::::P 4 | * S:::::SSSSSS::::::ST:::::::::::::::::::::T OO:::::::::::::OO P::::::PPPPPP:::::P 5 | * S:::::S SSSSSSST:::::TT:::::::TT:::::TO:::::::OOO:::::::OPP:::::P P:::::P 6 | * S:::::S TTTTTT T:::::T TTTTTTO::::::O O::::::O P::::P P:::::P 7 | * S:::::S T:::::T O:::::O O:::::O P::::P P:::::P 8 | * S::::SSSS T:::::T O:::::O O:::::O P::::PPPPPP:::::P 9 | * SS::::::SSSSS T:::::T O:::::O O:::::O P:::::::::::::PP 10 | * SSS::::::::SS T:::::T O:::::O O:::::O P::::PPPPPPPPP 11 | * SSSSSS::::S T:::::T O:::::O O:::::O P::::P 12 | * S:::::S T:::::T O:::::O O:::::O P::::P 13 | * S:::::S T:::::T O::::::O O::::::O P::::P 14 | * SSSSSSS S:::::S TT:::::::TT O:::::::OOO:::::::OPP::::::PP 15 | * S::::::SSSSSS:::::S T:::::::::T OO:::::::::::::OO P::::::::P 16 | * S:::::::::::::::SS T:::::::::T OO:::::::::OO P::::::::P 17 | * SSSSSSSSSSSSSSS TTTTTTTTTTT OOOOOOOOO PPPPPPPPPP 18 | * 19 | * 20 | * Please don't read this code. Reading this code is not in the spirit of the 21 | * assignment. You'll learn less and you'll also be letting us down. So please 22 | * just attempt the assignment without looking at these files. We trust you to 23 | * act honestly. Thanks! <3 24 | * 25 | * - Feross and the CS 253 course staff 26 | */ 27 | 28 | const { createServer } = require('../common/server') 29 | const { getResults } = require('../common/caloogle') 30 | 31 | const { router } = createServer(4020, __dirname) 32 | 33 | router.get('/', async (req, res) => { 34 | res.render('caloogle-home-page') 35 | }) 36 | 37 | router.get('/search', async (req, res) => { 38 | let q = req.query.q 39 | if (q == null) q = '' 40 | 41 | q = q.replace(/script/i, '') 42 | 43 | const results = await getResults(q) 44 | res.render('caloogle-search-page', { q, results }) 45 | }) 46 | -------------------------------------------------------------------------------- /exercises/03/server.js: -------------------------------------------------------------------------------- 1 | /** 2 | * SSSSSSSSSSSSSSS TTTTTTTTTTTTTTTTTTTTTTT OOOOOOOOO PPPPPPPPPPPPPPPPP 3 | * SS:::::::::::::::ST:::::::::::::::::::::T OO:::::::::OO P::::::::::::::::P 4 | * S:::::SSSSSS::::::ST:::::::::::::::::::::T OO:::::::::::::OO P::::::PPPPPP:::::P 5 | * S:::::S SSSSSSST:::::TT:::::::TT:::::TO:::::::OOO:::::::OPP:::::P P:::::P 6 | * S:::::S TTTTTT T:::::T TTTTTTO::::::O O::::::O P::::P P:::::P 7 | * S:::::S T:::::T O:::::O O:::::O P::::P P:::::P 8 | * S::::SSSS T:::::T O:::::O O:::::O P::::PPPPPP:::::P 9 | * SS::::::SSSSS T:::::T O:::::O O:::::O P:::::::::::::PP 10 | * SSS::::::::SS T:::::T O:::::O O:::::O P::::PPPPPPPPP 11 | * SSSSSS::::S T:::::T O:::::O O:::::O P::::P 12 | * S:::::S T:::::T O:::::O O:::::O P::::P 13 | * S:::::S T:::::T O::::::O O::::::O P::::P 14 | * SSSSSSS S:::::S TT:::::::TT O:::::::OOO:::::::OPP::::::PP 15 | * S::::::SSSSSS:::::S T:::::::::T OO:::::::::::::OO P::::::::P 16 | * S:::::::::::::::SS T:::::::::T OO:::::::::OO P::::::::P 17 | * SSSSSSSSSSSSSSS TTTTTTTTTTT OOOOOOOOO PPPPPPPPPP 18 | * 19 | * 20 | * Please don't read this code. Reading this code is not in the spirit of the 21 | * assignment. You'll learn less and you'll also be letting us down. So please 22 | * just attempt the assignment without looking at these files. We trust you to 23 | * act honestly. Thanks! <3 24 | * 25 | * - Feross and the CS 253 course staff 26 | */ 27 | 28 | const { createServer } = require('../common/server') 29 | const { getResults } = require('../common/caloogle') 30 | 31 | const { router } = createServer(4030, __dirname) 32 | 33 | router.get('/', async (req, res) => { 34 | res.render('caloogle-home-page') 35 | }) 36 | 37 | router.get('/search', async (req, res) => { 38 | let q = req.query.q 39 | if (q == null) q = '' 40 | 41 | q = q.replace(/script/gi, '') 42 | 43 | const results = await getResults(q) 44 | res.render('caloogle-search-page', { q, results }) 45 | }) 46 | -------------------------------------------------------------------------------- /exercises/08/server.js: -------------------------------------------------------------------------------- 1 | /** 2 | * SSSSSSSSSSSSSSS TTTTTTTTTTTTTTTTTTTTTTT OOOOOOOOO PPPPPPPPPPPPPPPPP 3 | * SS:::::::::::::::ST:::::::::::::::::::::T OO:::::::::OO P::::::::::::::::P 4 | * S:::::SSSSSS::::::ST:::::::::::::::::::::T OO:::::::::::::OO P::::::PPPPPP:::::P 5 | * S:::::S SSSSSSST:::::TT:::::::TT:::::TO:::::::OOO:::::::OPP:::::P P:::::P 6 | * S:::::S TTTTTT T:::::T TTTTTTO::::::O O::::::O P::::P P:::::P 7 | * S:::::S T:::::T O:::::O O:::::O P::::P P:::::P 8 | * S::::SSSS T:::::T O:::::O O:::::O P::::PPPPPP:::::P 9 | * SS::::::SSSSS T:::::T O:::::O O:::::O P:::::::::::::PP 10 | * SSS::::::::SS T:::::T O:::::O O:::::O P::::PPPPPPPPP 11 | * SSSSSS::::S T:::::T O:::::O O:::::O P::::P 12 | * S:::::S T:::::T O:::::O O:::::O P::::P 13 | * S:::::S T:::::T O::::::O O::::::O P::::P 14 | * SSSSSSS S:::::S TT:::::::TT O:::::::OOO:::::::OPP::::::PP 15 | * S::::::SSSSSS:::::S T:::::::::T OO:::::::::::::OO P::::::::P 16 | * S:::::::::::::::SS T:::::::::T OO:::::::::OO P::::::::P 17 | * SSSSSSSSSSSSSSS TTTTTTTTTTT OOOOOOOOO PPPPPPPPPP 18 | * 19 | * 20 | * Please don't read this code. Reading this code is not in the spirit of the 21 | * assignment. You'll learn less and you'll also be letting us down. So please 22 | * just attempt the assignment without looking at these files. We trust you to 23 | * act honestly. Thanks! <3 24 | * 25 | * - Feross and the CS 253 course staff 26 | */ 27 | 28 | const { createServer } = require('../common/server') 29 | const { getResults } = require('../common/caloogle') 30 | 31 | const { router } = createServer(4080, __dirname) 32 | 33 | router.get('/', async (req, res) => { 34 | res.render('caloogle-home-page') 35 | }) 36 | 37 | router.get('/search', async (req, res) => { 38 | let q = req.query.q 39 | if (q == null) q = '' 40 | 41 | q = q.replace(//, '') 42 | 43 | const results = await getResults(q) 44 | res.render('caloogle-search-page', { q, results }) 45 | }) 46 | -------------------------------------------------------------------------------- /exercises/11/server.js: -------------------------------------------------------------------------------- 1 | /** 2 | * SSSSSSSSSSSSSSS TTTTTTTTTTTTTTTTTTTTTTT OOOOOOOOO PPPPPPPPPPPPPPPPP 3 | * SS:::::::::::::::ST:::::::::::::::::::::T OO:::::::::OO P::::::::::::::::P 4 | * S:::::SSSSSS::::::ST:::::::::::::::::::::T OO:::::::::::::OO P::::::PPPPPP:::::P 5 | * S:::::S SSSSSSST:::::TT:::::::TT:::::TO:::::::OOO:::::::OPP:::::P P:::::P 6 | * S:::::S TTTTTT T:::::T TTTTTTO::::::O O::::::O P::::P P:::::P 7 | * S:::::S T:::::T O:::::O O:::::O P::::P P:::::P 8 | * S::::SSSS T:::::T O:::::O O:::::O P::::PPPPPP:::::P 9 | * SS::::::SSSSS T:::::T O:::::O O:::::O P:::::::::::::PP 10 | * SSS::::::::SS T:::::T O:::::O O:::::O P::::PPPPPPPPP 11 | * SSSSSS::::S T:::::T O:::::O O:::::O P::::P 12 | * S:::::S T:::::T O:::::O O:::::O P::::P 13 | * S:::::S T:::::T O::::::O O::::::O P::::P 14 | * SSSSSSS S:::::S TT:::::::TT O:::::::OOO:::::::OPP::::::PP 15 | * S::::::SSSSSS:::::S T:::::::::T OO:::::::::::::OO P::::::::P 16 | * S:::::::::::::::SS T:::::::::T OO:::::::::OO P::::::::P 17 | * SSSSSSSSSSSSSSS TTTTTTTTTTT OOOOOOOOO PPPPPPPPPP 18 | * 19 | * 20 | * Please don't read this code. Reading this code is not in the spirit of the 21 | * assignment. You'll learn less and you'll also be letting us down. So please 22 | * just attempt the assignment without looking at these files. We trust you to 23 | * act honestly. Thanks! <3 24 | * 25 | * - Feross and the CS 253 course staff 26 | */ 27 | 28 | const { createServer } = require('../common/server') 29 | const { getResults } = require('../common/caloogle') 30 | 31 | const { router } = createServer(4110, __dirname) 32 | 33 | router.get('/', async (req, res) => { 34 | res.render('caloogle-home-page') 35 | }) 36 | 37 | router.get('/search', async (req, res) => { 38 | let q = req.query.q 39 | if (q == null) q = '' 40 | 41 | const rawQ = q 42 | q = q.replace(/"/, '"') 43 | 44 | const results = await getResults(q) 45 | res.render('caloogle-search-page2', { q, rawQ, results }) 46 | }) 47 | -------------------------------------------------------------------------------- /exercises/12/server.js: -------------------------------------------------------------------------------- 1 | /** 2 | * SSSSSSSSSSSSSSS TTTTTTTTTTTTTTTTTTTTTTT OOOOOOOOO PPPPPPPPPPPPPPPPP 3 | * SS:::::::::::::::ST:::::::::::::::::::::T OO:::::::::OO P::::::::::::::::P 4 | * S:::::SSSSSS::::::ST:::::::::::::::::::::T OO:::::::::::::OO P::::::PPPPPP:::::P 5 | * S:::::S SSSSSSST:::::TT:::::::TT:::::TO:::::::OOO:::::::OPP:::::P P:::::P 6 | * S:::::S TTTTTT T:::::T TTTTTTO::::::O O::::::O P::::P P:::::P 7 | * S:::::S T:::::T O:::::O O:::::O P::::P P:::::P 8 | * S::::SSSS T:::::T O:::::O O:::::O P::::PPPPPP:::::P 9 | * SS::::::SSSSS T:::::T O:::::O O:::::O P:::::::::::::PP 10 | * SSS::::::::SS T:::::T O:::::O O:::::O P::::PPPPPPPPP 11 | * SSSSSS::::S T:::::T O:::::O O:::::O P::::P 12 | * S:::::S T:::::T O:::::O O:::::O P::::P 13 | * S:::::S T:::::T O::::::O O::::::O P::::P 14 | * SSSSSSS S:::::S TT:::::::TT O:::::::OOO:::::::OPP::::::PP 15 | * S::::::SSSSSS:::::S T:::::::::T OO:::::::::::::OO P::::::::P 16 | * S:::::::::::::::SS T:::::::::T OO:::::::::OO P::::::::P 17 | * SSSSSSSSSSSSSSS TTTTTTTTTTT OOOOOOOOO PPPPPPPPPP 18 | * 19 | * 20 | * Please don't read this code. Reading this code is not in the spirit of the 21 | * assignment. You'll learn less and you'll also be letting us down. So please 22 | * just attempt the assignment without looking at these files. We trust you to 23 | * act honestly. Thanks! <3 24 | * 25 | * - Feross and the CS 253 course staff 26 | */ 27 | 28 | const { createServer } = require('../common/server') 29 | const { getResults } = require('../common/caloogle') 30 | 31 | const { router } = createServer(4120, __dirname) 32 | 33 | router.get('/', async (req, res) => { 34 | res.render('caloogle-home-page') 35 | }) 36 | 37 | router.get('/search', async (req, res) => { 38 | let q = req.query.q 39 | if (q == null) q = '' 40 | 41 | const rawQ = q 42 | q = q.replace(/"/g, '"') 43 | 44 | const results = await getResults(q) 45 | res.render('caloogle-search-page3', { q, rawQ, results }) 46 | }) 47 | -------------------------------------------------------------------------------- /exercises/10/server.js: -------------------------------------------------------------------------------- 1 | /** 2 | * SSSSSSSSSSSSSSS TTTTTTTTTTTTTTTTTTTTTTT OOOOOOOOO PPPPPPPPPPPPPPPPP 3 | * SS:::::::::::::::ST:::::::::::::::::::::T OO:::::::::OO P::::::::::::::::P 4 | * S:::::SSSSSS::::::ST:::::::::::::::::::::T OO:::::::::::::OO P::::::PPPPPP:::::P 5 | * S:::::S SSSSSSST:::::TT:::::::TT:::::TO:::::::OOO:::::::OPP:::::P P:::::P 6 | * S:::::S TTTTTT T:::::T TTTTTTO::::::O O::::::O P::::P P:::::P 7 | * S:::::S T:::::T O:::::O O:::::O P::::P P:::::P 8 | * S::::SSSS T:::::T O:::::O O:::::O P::::PPPPPP:::::P 9 | * SS::::::SSSSS T:::::T O:::::O O:::::O P:::::::::::::PP 10 | * SSS::::::::SS T:::::T O:::::O O:::::O P::::PPPPPPPPP 11 | * SSSSSS::::S T:::::T O:::::O O:::::O P::::P 12 | * S:::::S T:::::T O:::::O O:::::O P::::P 13 | * S:::::S T:::::T O::::::O O::::::O P::::P 14 | * SSSSSSS S:::::S TT:::::::TT O:::::::OOO:::::::OPP::::::PP 15 | * S::::::SSSSSS:::::S T:::::::::T OO:::::::::::::OO P::::::::P 16 | * S:::::::::::::::SS T:::::::::T OO:::::::::OO P::::::::P 17 | * SSSSSSSSSSSSSSS TTTTTTTTTTT OOOOOOOOO PPPPPPPPPP 18 | * 19 | * 20 | * Please don't read this code. Reading this code is not in the spirit of the 21 | * assignment. You'll learn less and you'll also be letting us down. So please 22 | * just attempt the assignment without looking at these files. We trust you to 23 | * act honestly. Thanks! <3 24 | * 25 | * - Feross and the CS 253 course staff 26 | */ 27 | 28 | const { createServer } = require('../common/server') 29 | const { getResults, htmlElementEscape } = require('../common/caloogle') 30 | 31 | const { router } = createServer(4100, __dirname) 32 | 33 | router.get('/', async (req, res) => { 34 | res.render('caloogle-home-page') 35 | }) 36 | 37 | router.get('/search', async (req, res) => { 38 | let q = req.query.q 39 | if (q == null) q = '' 40 | 41 | const rawQ = q 42 | q = htmlElementEscape(q) 43 | 44 | const results = await getResults(q) 45 | res.render('caloogle-search-page2', { q, rawQ, results }) 46 | }) 47 | -------------------------------------------------------------------------------- /exercises/04/server.js: -------------------------------------------------------------------------------- 1 | /** 2 | * SSSSSSSSSSSSSSS TTTTTTTTTTTTTTTTTTTTTTT OOOOOOOOO PPPPPPPPPPPPPPPPP 3 | * SS:::::::::::::::ST:::::::::::::::::::::T OO:::::::::OO P::::::::::::::::P 4 | * S:::::SSSSSS::::::ST:::::::::::::::::::::T OO:::::::::::::OO P::::::PPPPPP:::::P 5 | * S:::::S SSSSSSST:::::TT:::::::TT:::::TO:::::::OOO:::::::OPP:::::P P:::::P 6 | * S:::::S TTTTTT T:::::T TTTTTTO::::::O O::::::O P::::P P:::::P 7 | * S:::::S T:::::T O:::::O O:::::O P::::P P:::::P 8 | * S::::SSSS T:::::T O:::::O O:::::O P::::PPPPPP:::::P 9 | * SS::::::SSSSS T:::::T O:::::O O:::::O P:::::::::::::PP 10 | * SSS::::::::SS T:::::T O:::::O O:::::O P::::PPPPPPPPP 11 | * SSSSSS::::S T:::::T O:::::O O:::::O P::::P 12 | * S:::::S T:::::T O:::::O O:::::O P::::P 13 | * S:::::S T:::::T O::::::O O::::::O P::::P 14 | * SSSSSSS S:::::S TT:::::::TT O:::::::OOO:::::::OPP::::::PP 15 | * S::::::SSSSSS:::::S T:::::::::T OO:::::::::::::OO P::::::::P 16 | * S:::::::::::::::SS T:::::::::T OO:::::::::OO P::::::::P 17 | * SSSSSSSSSSSSSSS TTTTTTTTTTT OOOOOOOOO PPPPPPPPPP 18 | * 19 | * 20 | * Please don't read this code. Reading this code is not in the spirit of the 21 | * assignment. You'll learn less and you'll also be letting us down. So please 22 | * just attempt the assignment without looking at these files. We trust you to 23 | * act honestly. Thanks! <3 24 | * 25 | * - Feross and the CS 253 course staff 26 | */ 27 | 28 | const { createServer } = require('../common/server') 29 | const { getResults } = require('../common/caloogle') 30 | 31 | const { router } = createServer(4040, __dirname) 32 | 33 | router.get('/', async (req, res) => { 34 | res.render('caloogle-home-page') 35 | }) 36 | 37 | router.get('/search', async (req, res) => { 38 | let q = req.query.q 39 | if (q == null) q = '' 40 | 41 | let oldQ 42 | while (q !== oldQ) { 43 | oldQ = q 44 | q = q.replace(/script/g, '') 45 | } 46 | 47 | const results = await getResults(q) 48 | res.render('caloogle-search-page', { q, results }) 49 | }) 50 | -------------------------------------------------------------------------------- /exercises/06/server.js: -------------------------------------------------------------------------------- 1 | /** 2 | * SSSSSSSSSSSSSSS TTTTTTTTTTTTTTTTTTTTTTT OOOOOOOOO PPPPPPPPPPPPPPPPP 3 | * SS:::::::::::::::ST:::::::::::::::::::::T OO:::::::::OO P::::::::::::::::P 4 | * S:::::SSSSSS::::::ST:::::::::::::::::::::T OO:::::::::::::OO P::::::PPPPPP:::::P 5 | * S:::::S SSSSSSST:::::TT:::::::TT:::::TO:::::::OOO:::::::OPP:::::P P:::::P 6 | * S:::::S TTTTTT T:::::T TTTTTTO::::::O O::::::O P::::P P:::::P 7 | * S:::::S T:::::T O:::::O O:::::O P::::P P:::::P 8 | * S::::SSSS T:::::T O:::::O O:::::O P::::PPPPPP:::::P 9 | * SS::::::SSSSS T:::::T O:::::O O:::::O P:::::::::::::PP 10 | * SSS::::::::SS T:::::T O:::::O O:::::O P::::PPPPPPPPP 11 | * SSSSSS::::S T:::::T O:::::O O:::::O P::::P 12 | * S:::::S T:::::T O:::::O O:::::O P::::P 13 | * S:::::S T:::::T O::::::O O::::::O P::::P 14 | * SSSSSSS S:::::S TT:::::::TT O:::::::OOO:::::::OPP::::::PP 15 | * S::::::SSSSSS:::::S T:::::::::T OO:::::::::::::OO P::::::::P 16 | * S:::::::::::::::SS T:::::::::T OO:::::::::OO P::::::::P 17 | * SSSSSSSSSSSSSSS TTTTTTTTTTT OOOOOOOOO PPPPPPPPPP 18 | * 19 | * 20 | * Please don't read this code. Reading this code is not in the spirit of the 21 | * assignment. You'll learn less and you'll also be letting us down. So please 22 | * just attempt the assignment without looking at these files. We trust you to 23 | * act honestly. Thanks! <3 24 | * 25 | * - Feross and the CS 253 course staff 26 | */ 27 | 28 | const { createServer } = require('../common/server') 29 | const { getResults } = require('../common/caloogle') 30 | 31 | const { router } = createServer(4060, __dirname) 32 | 33 | router.get('/', async (req, res) => { 34 | res.render('caloogle-home-page') 35 | }) 36 | 37 | router.get('/search', async (req, res) => { 38 | let q = req.query.q 39 | if (q == null) q = '' 40 | 41 | let oldQ 42 | while (q !== oldQ) { 43 | oldQ = q 44 | q = q.replace(/script/gi, '') 45 | } 46 | 47 | const results = await getResults(q) 48 | res.render('caloogle-search-page', { q, results }) 49 | }) 50 | -------------------------------------------------------------------------------- /exercises/05/server.js: -------------------------------------------------------------------------------- 1 | /** 2 | * SSSSSSSSSSSSSSS TTTTTTTTTTTTTTTTTTTTTTT OOOOOOOOO PPPPPPPPPPPPPPPPP 3 | * SS:::::::::::::::ST:::::::::::::::::::::T OO:::::::::OO P::::::::::::::::P 4 | * S:::::SSSSSS::::::ST:::::::::::::::::::::T OO:::::::::::::OO P::::::PPPPPP:::::P 5 | * S:::::S SSSSSSST:::::TT:::::::TT:::::TO:::::::OOO:::::::OPP:::::P P:::::P 6 | * S:::::S TTTTTT T:::::T TTTTTTO::::::O O::::::O P::::P P:::::P 7 | * S:::::S T:::::T O:::::O O:::::O P::::P P:::::P 8 | * S::::SSSS T:::::T O:::::O O:::::O P::::PPPPPP:::::P 9 | * SS::::::SSSSS T:::::T O:::::O O:::::O P:::::::::::::PP 10 | * SSS::::::::SS T:::::T O:::::O O:::::O P::::PPPPPPPPP 11 | * SSSSSS::::S T:::::T O:::::O O:::::O P::::P 12 | * S:::::S T:::::T O:::::O O:::::O P::::P 13 | * S:::::S T:::::T O::::::O O::::::O P::::P 14 | * SSSSSSS S:::::S TT:::::::TT O:::::::OOO:::::::OPP::::::PP 15 | * S::::::SSSSSS:::::S T:::::::::T OO:::::::::::::OO P::::::::P 16 | * S:::::::::::::::SS T:::::::::T OO:::::::::OO P::::::::P 17 | * SSSSSSSSSSSSSSS TTTTTTTTTTT OOOOOOOOO PPPPPPPPPP 18 | * 19 | * 20 | * Please don't read this code. Reading this code is not in the spirit of the 21 | * assignment. You'll learn less and you'll also be letting us down. So please 22 | * just attempt the assignment without looking at these files. We trust you to 23 | * act honestly. Thanks! <3 24 | * 25 | * - Feross and the CS 253 course staff 26 | */ 27 | 28 | const { createServer } = require('../common/server') 29 | const { getResults } = require('../common/caloogle') 30 | 31 | const { router } = createServer(4050, __dirname) 32 | 33 | router.get('/', async (req, res) => { 34 | res.render('caloogle-home-page') 35 | }) 36 | 37 | router.get('/search', async (req, res) => { 38 | let q = req.query.q 39 | if (q == null) q = '' 40 | 41 | let oldQ 42 | while (q !== oldQ) { 43 | oldQ = q 44 | q = q.replace(/script|SCRIPT/g, '') 45 | } 46 | 47 | const results = await getResults(q) 48 | res.render('caloogle-search-page', { q, results }) 49 | }) 50 | -------------------------------------------------------------------------------- /exercises/07/server.js: -------------------------------------------------------------------------------- 1 | /** 2 | * SSSSSSSSSSSSSSS TTTTTTTTTTTTTTTTTTTTTTT OOOOOOOOO PPPPPPPPPPPPPPPPP 3 | * SS:::::::::::::::ST:::::::::::::::::::::T OO:::::::::OO P::::::::::::::::P 4 | * S:::::SSSSSS::::::ST:::::::::::::::::::::T OO:::::::::::::OO P::::::PPPPPP:::::P 5 | * S:::::S SSSSSSST:::::TT:::::::TT:::::TO:::::::OOO:::::::OPP:::::P P:::::P 6 | * S:::::S TTTTTT T:::::T TTTTTTO::::::O O::::::O P::::P P:::::P 7 | * S:::::S T:::::T O:::::O O:::::O P::::P P:::::P 8 | * S::::SSSS T:::::T O:::::O O:::::O P::::PPPPPP:::::P 9 | * SS::::::SSSSS T:::::T O:::::O O:::::O P:::::::::::::PP 10 | * SSS::::::::SS T:::::T O:::::O O:::::O P::::PPPPPPPPP 11 | * SSSSSS::::S T:::::T O:::::O O:::::O P::::P 12 | * S:::::S T:::::T O:::::O O:::::O P::::P 13 | * S:::::S T:::::T O::::::O O::::::O P::::P 14 | * SSSSSSS S:::::S TT:::::::TT O:::::::OOO:::::::OPP::::::PP 15 | * S::::::SSSSSS:::::S T:::::::::T OO:::::::::::::OO P::::::::P 16 | * S:::::::::::::::SS T:::::::::T OO:::::::::OO P::::::::P 17 | * SSSSSSSSSSSSSSS TTTTTTTTTTT OOOOOOOOO PPPPPPPPPP 18 | * 19 | * 20 | * Please don't read this code. Reading this code is not in the spirit of the 21 | * assignment. You'll learn less and you'll also be letting us down. So please 22 | * just attempt the assignment without looking at these files. We trust you to 23 | * act honestly. Thanks! <3 24 | * 25 | * - Feross and the CS 253 course staff 26 | */ 27 | 28 | const { createServer } = require('../common/server') 29 | const { getResults } = require('../common/caloogle') 30 | 31 | const { router } = createServer(4070, __dirname) 32 | 33 | router.get('/', async (req, res) => { 34 | res.render('caloogle-home-page') 35 | }) 36 | 37 | router.get('/search', async (req, res) => { 38 | let q = req.query.q 39 | if (q == null) q = '' 40 | 41 | let oldQ 42 | while (q !== oldQ) { 43 | oldQ = q 44 | q = q.replace(/script|onerror=|onload=/gi, '') 45 | } 46 | 47 | const results = await getResults(q) 48 | res.render('caloogle-search-page', { q, results }) 49 | }) 50 | -------------------------------------------------------------------------------- /exercises/14/server.js: -------------------------------------------------------------------------------- 1 | /** 2 | * SSSSSSSSSSSSSSS TTTTTTTTTTTTTTTTTTTTTTT OOOOOOOOO PPPPPPPPPPPPPPPPP 3 | * SS:::::::::::::::ST:::::::::::::::::::::T OO:::::::::OO P::::::::::::::::P 4 | * S:::::SSSSSS::::::ST:::::::::::::::::::::T OO:::::::::::::OO P::::::PPPPPP:::::P 5 | * S:::::S SSSSSSST:::::TT:::::::TT:::::TO:::::::OOO:::::::OPP:::::P P:::::P 6 | * S:::::S TTTTTT T:::::T TTTTTTO::::::O O::::::O P::::P P:::::P 7 | * S:::::S T:::::T O:::::O O:::::O P::::P P:::::P 8 | * S::::SSSS T:::::T O:::::O O:::::O P::::PPPPPP:::::P 9 | * SS::::::SSSSS T:::::T O:::::O O:::::O P:::::::::::::PP 10 | * SSS::::::::SS T:::::T O:::::O O:::::O P::::PPPPPPPPP 11 | * SSSSSS::::S T:::::T O:::::O O:::::O P::::P 12 | * S:::::S T:::::T O:::::O O:::::O P::::P 13 | * S:::::S T:::::T O::::::O O::::::O P::::P 14 | * SSSSSSS S:::::S TT:::::::TT O:::::::OOO:::::::OPP::::::PP 15 | * S::::::SSSSSS:::::S T:::::::::T OO:::::::::::::OO P::::::::P 16 | * S:::::::::::::::SS T:::::::::T OO:::::::::OO P::::::::P 17 | * SSSSSSSSSSSSSSS TTTTTTTTTTT OOOOOOOOO PPPPPPPPPP 18 | * 19 | * 20 | * Please don't read this code. Reading this code is not in the spirit of the 21 | * assignment. You'll learn less and you'll also be letting us down. So please 22 | * just attempt the assignment without looking at these files. We trust you to 23 | * act honestly. Thanks! <3 24 | * 25 | * - Feross and the CS 253 course staff 26 | */ 27 | 28 | const { createServer } = require('../common/server') 29 | const { getResults, getLanguageVarsFromRequest } = require('../common/caloogle') 30 | 31 | const { router } = createServer(4140, __dirname) 32 | 33 | router.get('/', async (req, res) => { 34 | res.render('caloogle-home-page') 35 | }) 36 | 37 | router.get('/search', async (req, res) => { 38 | let q = req.query.q 39 | if (q == null) q = '' 40 | 41 | const rawQ = q 42 | 43 | const languageVars = getLanguageVarsFromRequest(req) 44 | 45 | const results = await getResults(q) 46 | res.render('caloogle-search-page4', { 47 | rawQ, 48 | results, 49 | ...languageVars 50 | }) 51 | }) 52 | -------------------------------------------------------------------------------- /exercises/15/server.js: -------------------------------------------------------------------------------- 1 | /** 2 | * SSSSSSSSSSSSSSS TTTTTTTTTTTTTTTTTTTTTTT OOOOOOOOO PPPPPPPPPPPPPPPPP 3 | * SS:::::::::::::::ST:::::::::::::::::::::T OO:::::::::OO P::::::::::::::::P 4 | * S:::::SSSSSS::::::ST:::::::::::::::::::::T OO:::::::::::::OO P::::::PPPPPP:::::P 5 | * S:::::S SSSSSSST:::::TT:::::::TT:::::TO:::::::OOO:::::::OPP:::::P P:::::P 6 | * S:::::S TTTTTT T:::::T TTTTTTO::::::O O::::::O P::::P P:::::P 7 | * S:::::S T:::::T O:::::O O:::::O P::::P P:::::P 8 | * S::::SSSS T:::::T O:::::O O:::::O P::::PPPPPP:::::P 9 | * SS::::::SSSSS T:::::T O:::::O O:::::O P:::::::::::::PP 10 | * SSS::::::::SS T:::::T O:::::O O:::::O P::::PPPPPPPPP 11 | * SSSSSS::::S T:::::T O:::::O O:::::O P::::P 12 | * S:::::S T:::::T O:::::O O:::::O P::::P 13 | * S:::::S T:::::T O::::::O O::::::O P::::P 14 | * SSSSSSS S:::::S TT:::::::TT O:::::::OOO:::::::OPP::::::PP 15 | * S::::::SSSSSS:::::S T:::::::::T OO:::::::::::::OO P::::::::P 16 | * S:::::::::::::::SS T:::::::::T OO:::::::::OO P::::::::P 17 | * SSSSSSSSSSSSSSS TTTTTTTTTTT OOOOOOOOO PPPPPPPPPP 18 | * 19 | * 20 | * Please don't read this code. Reading this code is not in the spirit of the 21 | * assignment. You'll learn less and you'll also be letting us down. So please 22 | * just attempt the assignment without looking at these files. We trust you to 23 | * act honestly. Thanks! <3 24 | * 25 | * - Feross and the CS 253 course staff 26 | */ 27 | 28 | const { createServer } = require('../common/server') 29 | const { getResults, htmlAttributeEscape, getLanguageVarsFromRequest } = require('../common/caloogle') 30 | 31 | const { router } = createServer(4150, __dirname) 32 | 33 | router.get('/', async (req, res) => { 34 | res.render('caloogle-home-page') 35 | }) 36 | 37 | router.get('/search', async (req, res) => { 38 | let q = req.query.q 39 | if (q == null) q = '' 40 | 41 | const rawQ = q 42 | q = htmlAttributeEscape(q) 43 | 44 | const languageVars = getLanguageVarsFromRequest(req) 45 | 46 | const results = await getResults(q) 47 | res.render('caloogle-search-page5', { 48 | q, 49 | rawQ, 50 | results, 51 | ...languageVars 52 | }) 53 | }) 54 | -------------------------------------------------------------------------------- /exercises/16/server.js: -------------------------------------------------------------------------------- 1 | /** 2 | * SSSSSSSSSSSSSSS TTTTTTTTTTTTTTTTTTTTTTT OOOOOOOOO PPPPPPPPPPPPPPPPP 3 | * SS:::::::::::::::ST:::::::::::::::::::::T OO:::::::::OO P::::::::::::::::P 4 | * S:::::SSSSSS::::::ST:::::::::::::::::::::T OO:::::::::::::OO P::::::PPPPPP:::::P 5 | * S:::::S SSSSSSST:::::TT:::::::TT:::::TO:::::::OOO:::::::OPP:::::P P:::::P 6 | * S:::::S TTTTTT T:::::T TTTTTTO::::::O O::::::O P::::P P:::::P 7 | * S:::::S T:::::T O:::::O O:::::O P::::P P:::::P 8 | * S::::SSSS T:::::T O:::::O O:::::O P::::PPPPPP:::::P 9 | * SS::::::SSSSS T:::::T O:::::O O:::::O P:::::::::::::PP 10 | * SSS::::::::SS T:::::T O:::::O O:::::O P::::PPPPPPPPP 11 | * SSSSSS::::S T:::::T O:::::O O:::::O P::::P 12 | * S:::::S T:::::T O:::::O O:::::O P::::P 13 | * S:::::S T:::::T O::::::O O::::::O P::::P 14 | * SSSSSSS S:::::S TT:::::::TT O:::::::OOO:::::::OPP::::::PP 15 | * S::::::SSSSSS:::::S T:::::::::T OO:::::::::::::OO P::::::::P 16 | * S:::::::::::::::SS T:::::::::T OO:::::::::OO P::::::::P 17 | * SSSSSSSSSSSSSSS TTTTTTTTTTT OOOOOOOOO PPPPPPPPPP 18 | * 19 | * 20 | * Please don't read this code. Reading this code is not in the spirit of the 21 | * assignment. You'll learn less and you'll also be letting us down. So please 22 | * just attempt the assignment without looking at these files. We trust you to 23 | * act honestly. Thanks! <3 24 | * 25 | * - Feross and the CS 253 course staff 26 | */ 27 | 28 | const { createServer } = require('../common/server') 29 | const { 30 | getResults, 31 | htmlAttributeEscape, 32 | getLanguageVarsFromRequest 33 | } = require('../common/caloogle') 34 | 35 | const { router } = createServer(4160, __dirname) 36 | 37 | router.get('/', async (req, res) => { 38 | res.render('caloogle-home-page') 39 | }) 40 | 41 | router.get('/search', async (req, res) => { 42 | let q = req.query.q 43 | if (q == null) q = '' 44 | 45 | const rawQ = q 46 | q = htmlAttributeEscape(q).replace(/<\//g, '') 47 | 48 | const languageVars = getLanguageVarsFromRequest(req) 49 | 50 | const results = await getResults(q) 51 | res.render('caloogle-search-page5', { 52 | q, 53 | rawQ, 54 | results, 55 | ...languageVars 56 | }) 57 | }) 58 | -------------------------------------------------------------------------------- /exercises/common/static/caloogle.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --blue: #4285F4; 3 | --green: #34A853; 4 | --yellow: #FBBC05; 5 | --red: #EA4335; 6 | } 7 | 8 | body { 9 | padding: 20px; 10 | } 11 | 12 | h1 { 13 | font-size: 50px; 14 | } 15 | 16 | .searchInput { 17 | border-radius: 9999px; 18 | outline: none; 19 | border: 2px solid black; 20 | font-size: 16px; 21 | padding: 5px 10px; 22 | width: 100%; 23 | max-width: 500px; 24 | } 25 | 26 | .searchInput:focus, .searchInput:active { 27 | border: 2px solid var(--blue); 28 | } 29 | 30 | button { 31 | font-size: 16px; 32 | margin: 15px 0; 33 | } 34 | 35 | .blue { 36 | color: var(--blue); 37 | } 38 | 39 | .green { 40 | color: var(--green); 41 | } 42 | 43 | .yellow { 44 | color: var(--yellow); 45 | } 46 | 47 | .red { 48 | color: var(--red); 49 | } 50 | 51 | .home-page { 52 | text-align: center; 53 | } 54 | 55 | .search-page { 56 | margin-left: auto; 57 | margin-right: auto; 58 | width: 100%; 59 | max-width: 800px; 60 | } 61 | 62 | .search-page .header > * { 63 | float: left; 64 | } 65 | 66 | .search-page .logo { 67 | width: 130px; 68 | margin: 15px 0; 69 | } 70 | 71 | .search-page .logo a { 72 | text-decoration: none; 73 | } 74 | 75 | .search-page form { 76 | width: calc(100% - 130px); 77 | } 78 | 79 | .search-page input { 80 | width: calc(100% - 110px); 81 | } 82 | 83 | .search-page button { 84 | width: 100px; 85 | } 86 | 87 | .search-page .result { 88 | margin: 20px 0; 89 | } 90 | 91 | .search-page .result .link { 92 | color: green; 93 | display: block; 94 | text-decoration: none; 95 | overflow-wrap: break-word; 96 | } 97 | 98 | .search-page .result .title { 99 | font-size: 16px; 100 | font-weight: bold; 101 | color: #1a0dab; 102 | text-decoration: none; 103 | } 104 | 105 | .competition img { 106 | width: 150px; 107 | } 108 | 109 | .searchInput { 110 | border-radius: 9999px; 111 | outline: none; 112 | border: 2px solid black; 113 | font-size: 16px; 114 | padding: 5px 10px; 115 | width: 100%; 116 | max-width: 500px; 117 | } 118 | 119 | .searchInput:focus, .searchInput:active { 120 | border: 2px solid var(--blue); 121 | } 122 | 123 | body.guestbook { 124 | background: url('bg-stars.gif'); 125 | color: white; 126 | } 127 | 128 | div.guestbook-page { 129 | text-align: center; 130 | } 131 | 132 | div.guestbook-page input { 133 | box-shadow: inset 0 0 10px #000000; 134 | border-radius: 999px; 135 | font-size: 20px; 136 | padding: 15px; 137 | width: 400px; 138 | } 139 | -------------------------------------------------------------------------------- /exercises/01/problem.md: -------------------------------------------------------------------------------- 1 | You're the founder of an innovative [world-changing startup](https://tiffzhang.com/startup/)! It has something to do with blockchain in the cloud. It's like Uber, but for cats. Or, maybe it's like Airbnb, but for gamification. It's social, mobile, and local. The tech press says that it's disruptive and revolutionary. Really, it has everything that a user could want. But you're still iterating in stealth mode using the Lean Startup™️ method until you find product-market fit and become a unicorn! 🦄 2 | 3 | But... one day, out of nowhere, disaster strikes! You see that your biggest competitor has released a product that is just like what you've been building. But they beat you to market! 4 | 5 | You suspect that they might have cut some corners when it comes to security in order to release their product so quickly. Not a good idea. You decide to put the "hacker" into "growth hacker" and teach them a lesson about taking web security seriously. 6 | 7 | Perhaps if your competitor sees their users getting attacked in the wild, you can convince them to unlaunch their site. This would give your team the much-needed time they need to launch your superior product! 😈 8 | 9 | You decide to check out their website and look for a Reflected XSS vulnerability. 10 | 11 | ## Goal 12 | 13 | Find a way to inject a `) and port () of the local server. 8 | 9 | In addition to this workshop HTTP server, we've also running many other local HTTP servers which are vulnerable to attack in various ways. Most of the exercises you will complete involve attacking or defending these vulnerable local servers. For security, these local HTTP servers are only listening on the local interface (`127.0.0.1`) and should not be accessible to other users on your local network. This means that folks connected to e.g. the same cafe Wi-Fi as you cannot connect to `http://:` and try to attack these vulnerable local servers. 10 | 11 | ## What is your goal? 12 | 13 | We're doing client-side attacks in this assignment. Your goal is to come up with "attack inputs" that when entered into vulnerable websites allow you to execute code in the target's browser. 14 | 15 | With Reflected XSS, you want to find a way to encode the attack input into a URL that can be sent to a target. When the URL is visited, your attack input is extracted from the URL by the server-side (or potentially client-side) code and executed in the target's browser. 16 | 17 | With Stored XSS, you want to find a way to get your attack input stored more permanently, e.g. in the server's database, so that when your target visits a page constructed using this data at some point in the future, your attack code will execute in their browser. 18 | 19 | Usually, you can test your "attack inputs" by entering them into a form input field or encoding them into a URL parameter. Once you can execute code in the victim's browser, you can prove this by calling the `success()` function that we've created for you. Remember to save the attack inputs which you produce into the `SOLUTIONS.md` file. This is what you will submit for grading. 20 | 21 | ## A quick note for the devious among you (all of you?) 22 | 23 | Keep in mind when you design your attacks that you are attacking a server running on your own computer. So don't try to `rm -rf` the server thinking you're super clever. This will end badly for you! 🤣 This fact isn't super relevant for this assignment since you'll mainly be devising client-side attacks, but it's good to know what's going on nonetheless. 24 | 25 | ## Another note for the extra, extra devious among you 26 | 27 | We haven't attempted to secure this workshop from *you*. You have all the source code and it's running on your machine, so you are technically able to examine the source code. We ask you to avoid doing this since it'll just make the assignments less fun for you. It is also possible for you to fake calls to `success()` or to modify the local state file to instantly "finish" all the challenges. Again, this wouldn't be much fun for you, so please don't do it. Since you have to submit your solutions in a separate text file anyway, this doesn't really help you anyway. 28 | 29 | ## Let's get going! 30 | 31 | Click this link to call success() and complete your first exercise. 32 | -------------------------------------------------------------------------------- /exercises/common/caloogle.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | getResults, 3 | getLanguageVarsFromRequest, 4 | htmlElementEscape, 5 | htmlAttributeEscape, 6 | htmlEscape 7 | } 8 | 9 | const got = require('got') 10 | 11 | async function getResults (q) { 12 | const searchUrl = `https://hn.algolia.com/api/v1/search?query=${encodeURIComponent(q)}` 13 | 14 | let body 15 | try { 16 | const res = await got(searchUrl, { json: true }) 17 | body = res.body 18 | } catch (err) {} 19 | 20 | const results = ((body && body.hits) || []) 21 | .map(hit => ({ 22 | title: hit.title || hit.story_title, 23 | url: hit.url || `https://news.ycombinator.com/item?id=${hit.objectID}` 24 | })) 25 | 26 | return results 27 | } 28 | 29 | function getLanguageVarsFromRequest (req) { 30 | let lang = req.query.lang 31 | if (lang == null) lang = 'en' // default language: english 32 | 33 | const url = new URL(req.url, 'http://example.com') 34 | const enUrl = new URL(url) 35 | enUrl.searchParams.set('lang', 'en') 36 | const esUrl = new URL(url, 'http://example.com') 37 | esUrl.searchParams.set('lang', 'es') 38 | const deUrl = new URL(url, 'http://example.com') 39 | deUrl.searchParams.set('lang', 'de') 40 | 41 | let searchText = 'Search' 42 | if (lang === 'es') { 43 | searchText = 'Buscar' 44 | } else if (lang === 'de') { 45 | searchText = 'Suche' 46 | } 47 | 48 | let languagesText = 'Languages' 49 | if (lang === 'es') { 50 | languagesText = 'Idiomas' 51 | } else if (lang === 'de') { 52 | languagesText = 'Sprachen' 53 | } 54 | 55 | let resultsForText = 'Results for' 56 | if (lang === 'es') { 57 | resultsForText = 'Resultados para' 58 | } else if (lang === 'de') { 59 | resultsForText = 'Ergebnisse für' 60 | } 61 | 62 | return { 63 | lang, 64 | enUrl, 65 | esUrl, 66 | deUrl, 67 | searchText, 68 | languagesText, 69 | resultsForText 70 | } 71 | } 72 | 73 | function htmlElementEscape (str) { 74 | return str 75 | // This is not for security, but because '&' is the HTML escape character 76 | // and we don't want the user's input to be treated as an escape sequence. 77 | .replace(/&/g, '&') 78 | 79 | // Without the '<' character, no HTML tags an be created. 80 | .replace(/ { 59 | res.redirect('/0') 60 | }) 61 | 62 | router.use(async (req, res) => { 63 | res.locals.exercises = exercises 64 | res.locals.config = await readConfig() 65 | 66 | return 'next' 67 | }) 68 | 69 | router.get('/:id', async (req, res) => { 70 | const id = Number(req.params.id) 71 | if (Number.isNaN(id)) return 'next' 72 | 73 | const { config } = res.locals 74 | 75 | let problemMd 76 | try { 77 | const problemMdPath = join(getExercisePath(id), 'problem.md') 78 | problemMd = await readFile(problemMdPath) 79 | } catch (err) { 80 | return 'next' 81 | } 82 | 83 | const exerciseName = exercises.find(exercise => exercise.id === id).name 84 | let content = `

Exercise ${id} – ${exerciseName}

\n` 85 | 86 | if (id > config.currentExcercise) { 87 | content += '

Please complete the earlier excercises first.

\n' 88 | } else { 89 | content += await remark() 90 | .use(remarkRecommended) 91 | .use(remarkHighlight) 92 | .use(remarkExternalLinks) 93 | .use(remarkHtml) 94 | .process(problemMd) 95 | } 96 | 97 | res 98 | .render('layout', { 99 | title: `Exercise ${id} – ${exerciseName}`, 100 | content 101 | }) 102 | }) 103 | 104 | const corsOpts = { 105 | origin: function (origin, cb) { 106 | const url = new URL(origin) 107 | if (url.hostname === 'localhost' || url.hostname === 'caloogle.xyz') { 108 | cb(null, true) 109 | } else { 110 | cb(new Error('Not allowed by CORS')) 111 | } 112 | } 113 | } 114 | 115 | router.post('/success/:id', cors(corsOpts), async (req, res) => { 116 | const id = Number(req.params.id) 117 | if (Number.isNaN(id)) return 'next' 118 | 119 | const config = await readConfig() 120 | 121 | if (id === config.currentExcercise) { 122 | config.currentExcercise = id + 1 123 | } 124 | await writeConfig(config) 125 | 126 | res.send(config) 127 | }) 128 | 129 | router.get('/trash', async (req, res) => { 130 | await trashConfig() 131 | res.redirect('/') 132 | }) 133 | 134 | router.get('*', async (req, res) => { 135 | res 136 | .status(404) 137 | .render('layout', { 138 | title: '404 – Page Not Found', 139 | content: '

404 – Page Not Found

' 140 | }) 141 | }) 142 | 143 | app.listen(PORT, '127.0.0.1', () => { 144 | const url = `http://localhost:${PORT}` 145 | console.log(`Server running on ${url}`) 146 | if (argv.open) open(url) 147 | }) 148 | } 149 | 150 | async function initExercises () { 151 | exercises.forEach(exercise => { 152 | if (exercise.heading || exercise.server === false) return 153 | const exerciseEntryPoint = join(getExercisePath(exercise.id), 'server') 154 | try { 155 | require(exerciseEntryPoint) 156 | } catch (err) { 157 | console.error(`Could not start server for excercise ${exercise.id}: ${err.message}`) 158 | } 159 | }) 160 | } 161 | 162 | function getExercisePath (id) { 163 | if (id < 10) id = `0${id}` 164 | else id = String(id) 165 | 166 | return join(EXERCISES_PATH, id) 167 | } 168 | -------------------------------------------------------------------------------- /static/common.css: -------------------------------------------------------------------------------- 1 | .clearfix:before, 2 | .clearfix:after { content: " "; display: table; } 3 | .clearfix:after { clear: both; } 4 | .clearfix { *zoom: 1; } 5 | 6 | * { 7 | box-sizing: border-box; 8 | } 9 | 10 | body { 11 | background-color: #FFFFFF; 12 | font-family: monospace; 13 | overflow-y: scroll; 14 | } 15 | 16 | h1, h2, h3, h4, h5, h6 { 17 | font-family: 'Share Tech Mono', monospace; 18 | margin: 1em 0; 19 | } 20 | 21 | iframe { 22 | border: 1px solid #999; 23 | background-color: #FFF; 24 | } 25 | 26 | .iframeNav { 27 | background-color: #f6f6f6; 28 | width: 100%; 29 | position: fixed; 30 | top: 0; 31 | left: 0; 32 | right: 0; 33 | color: #FFF; 34 | border-bottom: 1px solid #ddd; 35 | } 36 | 37 | .iframeNav .home, .iframeNav .newtab, .iframeNav .urlForm { 38 | padding: 5px 5px; 39 | display: block; 40 | float: left; 41 | } 42 | 43 | .iframeNav .home { 44 | background: center url('/home.png') no-repeat; 45 | background-size: 26px; 46 | opacity: 0.7; 47 | height: 40px; 48 | cursor: pointer; 49 | width: 40px; 50 | margin-left: 5px; 51 | } 52 | 53 | .iframeNav .newtab { 54 | background: center url('/newtab.png') no-repeat; 55 | background-size: 22px; 56 | opacity: 0.7; 57 | height: 40px; 58 | cursor: pointer; 59 | width: 40px; 60 | } 61 | 62 | .iframeNav .home:hover, .iframeNav .newtab:hover { 63 | opacity: 1; 64 | } 65 | 66 | .iframeNav .urlForm { 67 | width: calc(100% - 85px); 68 | height: 40px; 69 | } 70 | 71 | .iframeNav .url { 72 | font-size: 13px; 73 | border-radius: 9999px; 74 | border: 1px solid #e0e0e0; 75 | background-color: #e0e0e0; 76 | padding: 6px 12px; 77 | width: 100%; 78 | } 79 | 80 | .iframeNav .url:focus, .iframeNav .url:active { 81 | border: 1px solid #333; 82 | background-color: #fff; 83 | outline: 0; 84 | } 85 | 86 | .root { 87 | padding-top: 40px; 88 | } 89 | 90 | @-webkit-keyframes pulse { 91 | from { 92 | -webkit-transform: scale3d(1, 1, 1); 93 | transform: scale3d(1, 1, 1); 94 | } 95 | 96 | 50% { 97 | -webkit-transform: scale3d(1.05, 1.05, 1.05); 98 | transform: scale3d(1.05, 1.05, 1.05); 99 | } 100 | 101 | to { 102 | -webkit-transform: scale3d(1, 1, 1); 103 | transform: scale3d(1, 1, 1); 104 | } 105 | } 106 | 107 | @keyframes pulse { 108 | from { 109 | -webkit-transform: scale3d(1, 1, 1); 110 | transform: scale3d(1, 1, 1); 111 | } 112 | 113 | 50% { 114 | -webkit-transform: scale3d(1.05, 1.05, 1.05); 115 | transform: scale3d(1.05, 1.05, 1.05); 116 | } 117 | 118 | to { 119 | -webkit-transform: scale3d(1, 1, 1); 120 | transform: scale3d(1, 1, 1); 121 | } 122 | } 123 | 124 | .pulse { 125 | -webkit-animation-name: pulse; 126 | animation-name: pulse; 127 | } 128 | 129 | @-webkit-keyframes jello { 130 | from, 131 | 11.1%, 132 | to { 133 | -webkit-transform: translate3d(0, 0, 0); 134 | transform: translate3d(0, 0, 0); 135 | } 136 | 137 | 22.2% { 138 | -webkit-transform: skewX(-12.5deg) skewY(-12.5deg); 139 | transform: skewX(-12.5deg) skewY(-12.5deg); 140 | } 141 | 142 | 33.3% { 143 | -webkit-transform: skewX(6.25deg) skewY(6.25deg); 144 | transform: skewX(6.25deg) skewY(6.25deg); 145 | } 146 | 147 | 44.4% { 148 | -webkit-transform: skewX(-3.125deg) skewY(-3.125deg); 149 | transform: skewX(-3.125deg) skewY(-3.125deg); 150 | } 151 | 152 | 55.5% { 153 | -webkit-transform: skewX(1.5625deg) skewY(1.5625deg); 154 | transform: skewX(1.5625deg) skewY(1.5625deg); 155 | } 156 | 157 | 66.6% { 158 | -webkit-transform: skewX(-0.78125deg) skewY(-0.78125deg); 159 | transform: skewX(-0.78125deg) skewY(-0.78125deg); 160 | } 161 | 162 | 77.7% { 163 | -webkit-transform: skewX(0.390625deg) skewY(0.390625deg); 164 | transform: skewX(0.390625deg) skewY(0.390625deg); 165 | } 166 | 167 | 88.8% { 168 | -webkit-transform: skewX(-0.1953125deg) skewY(-0.1953125deg); 169 | transform: skewX(-0.1953125deg) skewY(-0.1953125deg); 170 | } 171 | } 172 | 173 | @keyframes jello { 174 | from, 175 | 11.1%, 176 | to { 177 | -webkit-transform: translate3d(0, 0, 0); 178 | transform: translate3d(0, 0, 0); 179 | } 180 | 181 | 22.2% { 182 | -webkit-transform: skewX(-12.5deg) skewY(-12.5deg); 183 | transform: skewX(-12.5deg) skewY(-12.5deg); 184 | } 185 | 186 | 33.3% { 187 | -webkit-transform: skewX(6.25deg) skewY(6.25deg); 188 | transform: skewX(6.25deg) skewY(6.25deg); 189 | } 190 | 191 | 44.4% { 192 | -webkit-transform: skewX(-3.125deg) skewY(-3.125deg); 193 | transform: skewX(-3.125deg) skewY(-3.125deg); 194 | } 195 | 196 | 55.5% { 197 | -webkit-transform: skewX(1.5625deg) skewY(1.5625deg); 198 | transform: skewX(1.5625deg) skewY(1.5625deg); 199 | } 200 | 201 | 66.6% { 202 | -webkit-transform: skewX(-0.78125deg) skewY(-0.78125deg); 203 | transform: skewX(-0.78125deg) skewY(-0.78125deg); 204 | } 205 | 206 | 77.7% { 207 | -webkit-transform: skewX(0.390625deg) skewY(0.390625deg); 208 | transform: skewX(0.390625deg) skewY(0.390625deg); 209 | } 210 | 211 | 88.8% { 212 | -webkit-transform: skewX(-0.1953125deg) skewY(-0.1953125deg); 213 | transform: skewX(-0.1953125deg) skewY(-0.1953125deg); 214 | } 215 | } 216 | 217 | .jello { 218 | -webkit-animation-name: jello; 219 | animation-name: jello; 220 | -webkit-transform-origin: center; 221 | transform-origin: center; 222 | } 223 | 224 | .animated { 225 | -webkit-animation-duration: 1s; 226 | animation-duration: 1s; 227 | -webkit-animation-fill-mode: both; 228 | animation-fill-mode: both; 229 | } 230 | 231 | .animated.infinite { 232 | -webkit-animation-iteration-count: infinite; 233 | animation-iteration-count: infinite; 234 | } 235 | --------------------------------------------------------------------------------