├── .dockerignore ├── .env ├── .gitignore ├── Dockerfile ├── README.md ├── assets ├── css │ ├── bootstrap-datepicker.min.css │ └── bootstrap.min.css ├── images │ ├── Bitcoin-BTC-icon.png │ └── Ethereum-ETH-icon.png ├── js │ ├── bootstrap-datepicker.min.js │ ├── bootstrap.min.js │ ├── jquery-3.6.0.min.js │ ├── popper.min.js │ └── run_prettify.js └── uploads │ └── default.png ├── controllers ├── auth_controller.js └── vuln_controller.js ├── docker-compose.yml ├── models ├── db.js ├── graphql-schema.js └── init.sql ├── package-lock.json ├── package.json ├── routes └── app.js ├── server.js ├── solutions ├── solutions.md └── solutions.pdf ├── views ├── add-user.ejs ├── cors-api-token.ejs ├── cors-edit-password.ejs ├── cross-site-websocket-hijacking.ejs ├── deserialization.ejs ├── edit_password.ejs ├── graphql-idor-show-profile.ejs ├── graphql-information-disclosure.ejs ├── graphql-update-profile.ejs ├── graphql-user-profile.ejs ├── includes │ ├── header.ejs │ └── navigation.ejs ├── index.ejs ├── jsonp-injection.ejs ├── jwt1.ejs ├── login.ejs ├── login_totp_verification.ejs ├── mongodb-notes.ejs ├── nosql-javascript-injection.ejs ├── notes.ejs ├── organization.ejs ├── ping.ejs ├── register.ejs ├── sqli-fixed.ejs ├── sqli.ejs ├── svg-xss.ejs ├── ticket-booking.ejs ├── ticket.ejs ├── totp.ejs ├── user-edit.ejs ├── webmessage-api-token-popup.ejs ├── webmessage-api-token.ejs ├── websocket-xss.ejs ├── xss.ejs └── xxe.ejs ├── vuln-nodejs-app.png └── vuln_react_app ├── .gitignore ├── package-lock.json ├── package.json ├── public ├── index.html ├── manifest.json ├── react-favicon.ico ├── react-logo192.png ├── react-logo512.png └── robots.txt └── src ├── App.css ├── App.js ├── App.test.js ├── MyComponents ├── Header.js ├── React_href_xss.js └── React_ref_innerHTML_xss.js ├── index.css ├── index.js ├── logo.svg ├── reportWebVitals.js └── setupTests.js /.dockerignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | .eslintrc.json 3 | .eslintrc.js 4 | node_modules/ 5 | vuln_react_app/build/ 6 | solutions/ 7 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | DB_PORT=3306 2 | DB_NAME=vuln_nodejs_app 3 | DB_USER=vuln_nodejs_user 4 | DB_PASS=passw0rd 5 | HOST_PORT=9000 6 | JWT_SECRET=secret 7 | MONGODB_SERVER=localhost 8 | MONGODB_ADMINUSERNAME= 9 | MONGODB_ADMINPASSWORD= -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | .eslintrc.json 3 | .eslintrc.js 4 | node_modules/ 5 | vuln_react_app/build/ 6 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16 2 | WORKDIR /usr/code 3 | COPY package*.json ./ 4 | RUN npm install 5 | RUN npm install nodemon -g 6 | COPY . . 7 | RUN npm run build 8 | EXPOSE 9000 9 | CMD ["node", "server.js"] 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

3 | Vulnerable NodeJS Application 4 |

5 | 6 | [![License](https://img.shields.io/badge/license--_red.svg)](https://opensource.org/licenses) 7 | [![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](https://github.com/github.com/payatu/vuln-nodejs-app/issues) 8 | [![version](https://img.shields.io/badge/version-v1.0-blue.svg?style=flat)](https://github.com/payatu/vuln-nodejs-app) 9 |

10 |
11 | 12 | Vulnerable NodeJS application is developed for web application penetration testers, developers and secure code review. It can be easily deployed using docker or by manual install complete steps are provided below. this application will help you in learning how to find vulnerabilities in web applicaiton using black box, white box approach and in learning how to fix them. 13 | 14 |

How to use it?

15 | 16 | - Black box testing: Deploy the appplication using docker and start solving the exercises. 17 | - Secure code review: Manually install the application this will allow you to use debugger while solving the exercises and will help you in finding vulnerabilities in application code. 18 | - Developers: Identify vulnerabilities in application code & try to fix them. 19 | 20 |

Tech Stack

21 | 22 | - NodeJS 23 | - Application design pattern: MVC 24 | - Web framework: Express 25 | - Template Engine: EJS 26 | - SQL Database: MySQL 27 | - NoSQL Database: MongoDB 28 | - React to cover ReactJS exercise 29 | - JWT for authentication 30 | - GraphQL 31 | - Socket.IO 32 | - Docker 33 | 34 |

Preview

35 | vuln-nodejs-app 36 |
37 |
38 | 39 |

Complete list of exercises

40 | 41 | 1. Command Injection 42 | 2. Insecure Deserialization 43 | 3. SQL Injection 44 | 4. XML external entity injection 45 | 5. XSS 46 | 6. Server Side Template Injection 47 | 7. JWT weak secret 48 | 8. Insecure direct object references 49 | 9. SSRF via PDF generator 50 | 10. postMessage XSS 51 | 11. postMessage CSRF 52 | 12. Information Disclosure using addEventListener 53 | 13. CORS Information Disclosure 54 | 14. CORS CSRF 55 | 15. 2FA Insecure Implementation 56 | 16. Cross-Site WebSocket Hijacking 57 | 17. WebSocket XSS 58 | 18. ReactJS href XSS 59 | 19. React ref-innerHTML XSS 60 | 20. NoSQL Injection 61 | 21. GraphQL Information Disclosure 62 | 22. GraphQL SQL Injection 63 | 23. GraphQL CSRF 64 | 24. GraphQL IDOR 65 | 25. XSS using SVG file uplaod 66 | 26. JSONP Injection 67 | 27. NoSQL Javascript Injection 68 | 69 |

Installation

70 | 71 |

Using docker-compose

72 | 73 | 1. Clone the repository. 74 | 75 | ```bash 76 | git clone https://github.com/payatu/vuln-nodejs-app.git 77 | cd ./vuln-nodejs-app 78 | ``` 79 | 2. Download and build the image. 80 | 81 | ```bash 82 | docker-compose up --build -d 83 | ``` 84 | 85 | 3. Start the application. 86 | ``` 87 | docker-compose up -d # Remove -d flag if you want to see logs 88 | ``` 89 | access the application http://localhost:9000 90 | 91 | ### Manual install 92 | 93 | 1. Clone the repository. 94 | 95 | ```bash 96 | git clone https://github.com/payatu/vuln-nodejs-app.git 97 | cd ./vuln-nodejs-app 98 | ``` 99 | 100 | 2. Create **MySQL** database. 101 | 102 | ```bash 103 | $ mysql -u -p 104 | 105 | mysql> create database vuln_nodejs_app; 106 | 107 | ``` 108 | 109 | 3. Update your MySQL and MongoDB database username and password inside **.env** file. 110 | 111 | ```html 112 | DB_PORT=3306 113 | DB_NAME=vuln_nodejs_app 114 | DB_USER=vuln_nodejs_user 115 | DB_PASS=passw0rd 116 | HOST_PORT=9000 117 | JWT_SECRET=secret 118 | MONGODB_SERVER=localhost 119 | MONGODB_ADMINUSERNAME= 120 | MONGODB_ADMINPASSWORD= 121 | ``` 122 | 123 | 124 | 4. Install dependencies. 125 | 126 | ```bash 127 | npm install 128 | ``` 129 | 130 | 5. Build React frontend. 131 | 132 | ```bash 133 | npm run build 134 | ``` 135 | 136 | 6. Start the server 137 | 138 | ```bash 139 | node server.js 140 | ``` 141 | You can now access the application at http://localhost:9000 142 |
143 |

Solutions

144 | 145 | Available in markdown and pdf format. 146 | 147 | **PDF:** solutions.pdf
148 | **Markdown:** solutions.md
149 |
150 |

Contribution

151 | 152 | Contributions are always appreciated and you can contribute in this project by following ways: 153 | 154 | - By adding more exercises. 155 | - By reporting issues or by solving open issues. 156 | - By making pull request it can be for anything (UI, New Feature, Fixing mistyped words etc) 157 | - Don't have time to code but have an idea of exercise open a issue and we will implement it. 158 | - By spreading the word about this project. 159 | - By doing write-up of exercise we will add your writeup in community write-ups section. 160 | 161 |

TODO:

162 | 163 | * Dockerize the application. 164 | * Add more vulnerabilites. 165 | * Use database to store user information. 166 | 167 |

Author

168 | @tauh33dkhan 169 | 170 | -------------------------------------------------------------------------------- /assets/css/bootstrap-datepicker.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Datepicker for Bootstrap v1.7.1 (https://github.com/uxsolutions/bootstrap-datepicker) 3 | * 4 | * Licensed under the Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0) 5 | */ 6 | 7 | .datepicker{padding:4px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;direction:ltr}.datepicker-inline{width:220px}.datepicker-rtl{direction:rtl}.datepicker-rtl.dropdown-menu{left:auto}.datepicker-rtl table tr td span{float:right}.datepicker-dropdown{top:0;left:0}.datepicker-dropdown:before{content:'';display:inline-block;border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid #999;border-top:0;border-bottom-color:rgba(0,0,0,.2);position:absolute}.datepicker-dropdown:after{content:'';display:inline-block;border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #fff;border-top:0;position:absolute}.datepicker-dropdown.datepicker-orient-left:before{left:6px}.datepicker-dropdown.datepicker-orient-left:after{left:7px}.datepicker-dropdown.datepicker-orient-right:before{right:6px}.datepicker-dropdown.datepicker-orient-right:after{right:7px}.datepicker-dropdown.datepicker-orient-bottom:before{top:-7px}.datepicker-dropdown.datepicker-orient-bottom:after{top:-6px}.datepicker-dropdown.datepicker-orient-top:before{bottom:-7px;border-bottom:0;border-top:7px solid #999}.datepicker-dropdown.datepicker-orient-top:after{bottom:-6px;border-bottom:0;border-top:6px solid #fff}.datepicker table{margin:0;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.datepicker td,.datepicker th{text-align:center;width:20px;height:20px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;border:none}.table-striped .datepicker table tr td,.table-striped .datepicker table tr th{background-color:transparent}.datepicker table tr td.day.focused,.datepicker table tr td.day:hover{background:#eee;cursor:pointer}.datepicker table tr td.new,.datepicker table tr td.old{color:#999}.datepicker table tr td.disabled,.datepicker table tr td.disabled:hover{background:0 0;color:#999;cursor:default}.datepicker table tr td.highlighted{background:#d9edf7;border-radius:0}.datepicker table tr td.today,.datepicker table tr td.today.disabled,.datepicker table tr td.today.disabled:hover,.datepicker table tr td.today:hover{background-color:#fde19a;background-image:-moz-linear-gradient(to bottom,#fdd49a,#fdf59a);background-image:-ms-linear-gradient(to bottom,#fdd49a,#fdf59a);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fdd49a),to(#fdf59a));background-image:-webkit-linear-gradient(to bottom,#fdd49a,#fdf59a);background-image:-o-linear-gradient(to bottom,#fdd49a,#fdf59a);background-image:linear-gradient(to bottom,#fdd49a,#fdf59a);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fdd49a', endColorstr='#fdf59a', GradientType=0);border-color:#fdf59a #fdf59a #fbed50;border-color:rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.25);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);color:#000}.datepicker table tr td.today.active,.datepicker table tr td.today.disabled,.datepicker table tr td.today.disabled.active,.datepicker table tr td.today.disabled.disabled,.datepicker table tr td.today.disabled:active,.datepicker table tr td.today.disabled:hover,.datepicker table tr td.today.disabled:hover.active,.datepicker table tr td.today.disabled:hover.disabled,.datepicker table tr td.today.disabled:hover:active,.datepicker table tr td.today.disabled:hover:hover,.datepicker table tr td.today.disabled:hover[disabled],.datepicker table tr td.today.disabled[disabled],.datepicker table tr td.today:active,.datepicker table tr td.today:hover,.datepicker table tr td.today:hover.active,.datepicker table tr td.today:hover.disabled,.datepicker table tr td.today:hover:active,.datepicker table tr td.today:hover:hover,.datepicker table tr td.today:hover[disabled],.datepicker table tr td.today[disabled]{background-color:#fdf59a}.datepicker table tr td.today.active,.datepicker table tr td.today.disabled.active,.datepicker table tr td.today.disabled:active,.datepicker table tr td.today.disabled:hover.active,.datepicker table tr td.today.disabled:hover:active,.datepicker table tr td.today:active,.datepicker table tr td.today:hover.active,.datepicker table tr td.today:hover:active{background-color:#fbf069\9}.datepicker table tr td.today:hover:hover{color:#000}.datepicker table tr td.today.active:hover{color:#fff}.datepicker table tr td.range,.datepicker table tr td.range.disabled,.datepicker table tr td.range.disabled:hover,.datepicker table tr td.range:hover{background:#eee;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.datepicker table tr td.range.today,.datepicker table tr td.range.today.disabled,.datepicker table tr td.range.today.disabled:hover,.datepicker table tr td.range.today:hover{background-color:#f3d17a;background-image:-moz-linear-gradient(to bottom,#f3c17a,#f3e97a);background-image:-ms-linear-gradient(to bottom,#f3c17a,#f3e97a);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f3c17a),to(#f3e97a));background-image:-webkit-linear-gradient(to bottom,#f3c17a,#f3e97a);background-image:-o-linear-gradient(to bottom,#f3c17a,#f3e97a);background-image:linear-gradient(to bottom,#f3c17a,#f3e97a);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#f3c17a', endColorstr='#f3e97a', GradientType=0);border-color:#f3e97a #f3e97a #edde34;border-color:rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.25);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.datepicker table tr td.range.today.active,.datepicker table tr td.range.today.disabled,.datepicker table tr td.range.today.disabled.active,.datepicker table tr td.range.today.disabled.disabled,.datepicker table tr td.range.today.disabled:active,.datepicker table tr td.range.today.disabled:hover,.datepicker table tr td.range.today.disabled:hover.active,.datepicker table tr td.range.today.disabled:hover.disabled,.datepicker table tr td.range.today.disabled:hover:active,.datepicker table tr td.range.today.disabled:hover:hover,.datepicker table tr td.range.today.disabled:hover[disabled],.datepicker table tr td.range.today.disabled[disabled],.datepicker table tr td.range.today:active,.datepicker table tr td.range.today:hover,.datepicker table tr td.range.today:hover.active,.datepicker table tr td.range.today:hover.disabled,.datepicker table tr td.range.today:hover:active,.datepicker table tr td.range.today:hover:hover,.datepicker table tr td.range.today:hover[disabled],.datepicker table tr td.range.today[disabled]{background-color:#f3e97a}.datepicker table tr td.range.today.active,.datepicker table tr td.range.today.disabled.active,.datepicker table tr td.range.today.disabled:active,.datepicker table tr td.range.today.disabled:hover.active,.datepicker table tr td.range.today.disabled:hover:active,.datepicker table tr td.range.today:active,.datepicker table tr td.range.today:hover.active,.datepicker table tr td.range.today:hover:active{background-color:#efe24b\9}.datepicker table tr td.selected,.datepicker table tr td.selected.disabled,.datepicker table tr td.selected.disabled:hover,.datepicker table tr td.selected:hover{background-color:#9e9e9e;background-image:-moz-linear-gradient(to bottom,#b3b3b3,grey);background-image:-ms-linear-gradient(to bottom,#b3b3b3,grey);background-image:-webkit-gradient(linear,0 0,0 100%,from(#b3b3b3),to(grey));background-image:-webkit-linear-gradient(to bottom,#b3b3b3,grey);background-image:-o-linear-gradient(to bottom,#b3b3b3,grey);background-image:linear-gradient(to bottom,#b3b3b3,grey);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#b3b3b3', endColorstr='#808080', GradientType=0);border-color:grey grey #595959;border-color:rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.25);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,.25)}.datepicker table tr td.selected.active,.datepicker table tr td.selected.disabled,.datepicker table tr td.selected.disabled.active,.datepicker table tr td.selected.disabled.disabled,.datepicker table tr td.selected.disabled:active,.datepicker table tr td.selected.disabled:hover,.datepicker table tr td.selected.disabled:hover.active,.datepicker table tr td.selected.disabled:hover.disabled,.datepicker table tr td.selected.disabled:hover:active,.datepicker table tr td.selected.disabled:hover:hover,.datepicker table tr td.selected.disabled:hover[disabled],.datepicker table tr td.selected.disabled[disabled],.datepicker table tr td.selected:active,.datepicker table tr td.selected:hover,.datepicker table tr td.selected:hover.active,.datepicker table tr td.selected:hover.disabled,.datepicker table tr td.selected:hover:active,.datepicker table tr td.selected:hover:hover,.datepicker table tr td.selected:hover[disabled],.datepicker table tr td.selected[disabled]{background-color:grey}.datepicker table tr td.selected.active,.datepicker table tr td.selected.disabled.active,.datepicker table tr td.selected.disabled:active,.datepicker table tr td.selected.disabled:hover.active,.datepicker table tr td.selected.disabled:hover:active,.datepicker table tr td.selected:active,.datepicker table tr td.selected:hover.active,.datepicker table tr td.selected:hover:active{background-color:#666\9}.datepicker table tr td.active,.datepicker table tr td.active.disabled,.datepicker table tr td.active.disabled:hover,.datepicker table tr td.active:hover{background-color:#006dcc;background-image:-moz-linear-gradient(to bottom,#08c,#04c);background-image:-ms-linear-gradient(to bottom,#08c,#04c);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#04c));background-image:-webkit-linear-gradient(to bottom,#08c,#04c);background-image:-o-linear-gradient(to bottom,#08c,#04c);background-image:linear-gradient(to bottom,#08c,#04c);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#08c', endColorstr='#0044cc', GradientType=0);border-color:#04c #04c #002a80;border-color:rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.25);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,.25)}.datepicker table tr td.active.active,.datepicker table tr td.active.disabled,.datepicker table tr td.active.disabled.active,.datepicker table tr td.active.disabled.disabled,.datepicker table tr td.active.disabled:active,.datepicker table tr td.active.disabled:hover,.datepicker table tr td.active.disabled:hover.active,.datepicker table tr td.active.disabled:hover.disabled,.datepicker table tr td.active.disabled:hover:active,.datepicker table tr td.active.disabled:hover:hover,.datepicker table tr td.active.disabled:hover[disabled],.datepicker table tr td.active.disabled[disabled],.datepicker table tr td.active:active,.datepicker table tr td.active:hover,.datepicker table tr td.active:hover.active,.datepicker table tr td.active:hover.disabled,.datepicker table tr td.active:hover:active,.datepicker table tr td.active:hover:hover,.datepicker table tr td.active:hover[disabled],.datepicker table tr td.active[disabled]{background-color:#04c}.datepicker table tr td.active.active,.datepicker table tr td.active.disabled.active,.datepicker table tr td.active.disabled:active,.datepicker table tr td.active.disabled:hover.active,.datepicker table tr td.active.disabled:hover:active,.datepicker table tr td.active:active,.datepicker table tr td.active:hover.active,.datepicker table tr td.active:hover:active{background-color:#039\9}.datepicker table tr td span{display:block;width:23%;height:54px;line-height:54px;float:left;margin:1%;cursor:pointer;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.datepicker table tr td span.focused,.datepicker table tr td span:hover{background:#eee}.datepicker table tr td span.disabled,.datepicker table tr td span.disabled:hover{background:0 0;color:#999;cursor:default}.datepicker table tr td span.active,.datepicker table tr td span.active.disabled,.datepicker table tr td span.active.disabled:hover,.datepicker table tr td span.active:hover{background-color:#006dcc;background-image:-moz-linear-gradient(to bottom,#08c,#04c);background-image:-ms-linear-gradient(to bottom,#08c,#04c);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#04c));background-image:-webkit-linear-gradient(to bottom,#08c,#04c);background-image:-o-linear-gradient(to bottom,#08c,#04c);background-image:linear-gradient(to bottom,#08c,#04c);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#08c', endColorstr='#0044cc', GradientType=0);border-color:#04c #04c #002a80;border-color:rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.25);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,.25)}.datepicker table tr td span.active.active,.datepicker table tr td span.active.disabled,.datepicker table tr td span.active.disabled.active,.datepicker table tr td span.active.disabled.disabled,.datepicker table tr td span.active.disabled:active,.datepicker table tr td span.active.disabled:hover,.datepicker table tr td span.active.disabled:hover.active,.datepicker table tr td span.active.disabled:hover.disabled,.datepicker table tr td span.active.disabled:hover:active,.datepicker table tr td span.active.disabled:hover:hover,.datepicker table tr td span.active.disabled:hover[disabled],.datepicker table tr td span.active.disabled[disabled],.datepicker table tr td span.active:active,.datepicker table tr td span.active:hover,.datepicker table tr td span.active:hover.active,.datepicker table tr td span.active:hover.disabled,.datepicker table tr td span.active:hover:active,.datepicker table tr td span.active:hover:hover,.datepicker table tr td span.active:hover[disabled],.datepicker table tr td span.active[disabled]{background-color:#04c}.datepicker table tr td span.active.active,.datepicker table tr td span.active.disabled.active,.datepicker table tr td span.active.disabled:active,.datepicker table tr td span.active.disabled:hover.active,.datepicker table tr td span.active.disabled:hover:active,.datepicker table tr td span.active:active,.datepicker table tr td span.active:hover.active,.datepicker table tr td span.active:hover:active{background-color:#039\9}.datepicker table tr td span.new,.datepicker table tr td span.old{color:#999}.datepicker .datepicker-switch{width:145px}.datepicker .datepicker-switch,.datepicker .next,.datepicker .prev,.datepicker tfoot tr th{cursor:pointer}.datepicker .datepicker-switch:hover,.datepicker .next:hover,.datepicker .prev:hover,.datepicker tfoot tr th:hover{background:#eee}.datepicker .next.disabled,.datepicker .prev.disabled{visibility:hidden}.datepicker .cw{font-size:10px;width:12px;padding:0 2px 0 5px;vertical-align:middle}.input-append.date .add-on,.input-prepend.date .add-on{cursor:pointer}.input-append.date .add-on i,.input-prepend.date .add-on i{margin-top:3px}.input-daterange input{text-align:center}.input-daterange input:first-child{-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px}.input-daterange input:last-child{-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0}.input-daterange .add-on{display:inline-block;width:auto;min-width:16px;height:18px;padding:4px 5px;font-weight:400;line-height:18px;text-align:center;text-shadow:0 1px 0 #fff;vertical-align:middle;background-color:#eee;border:1px solid #ccc;margin-left:-5px;margin-right:-5px} -------------------------------------------------------------------------------- /assets/images/Bitcoin-BTC-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/payatu/vuln-nodejs-app/bc3c90920ba4e1c668d954e5027d9ca814d2d5c6/assets/images/Bitcoin-BTC-icon.png -------------------------------------------------------------------------------- /assets/images/Ethereum-ETH-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/payatu/vuln-nodejs-app/bc3c90920ba4e1c668d954e5027d9ca814d2d5c6/assets/images/Ethereum-ETH-icon.png -------------------------------------------------------------------------------- /assets/js/popper.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) Federico Zivolo 2019 3 | Distributed under the MIT License (license terms are at http://opensource.org/licenses/MIT). 4 | */(function(e,t){'object'==typeof exports&&'undefined'!=typeof module?module.exports=t():'function'==typeof define&&define.amd?define(t):e.Popper=t()})(this,function(){'use strict';function e(e){return e&&'[object Function]'==={}.toString.call(e)}function t(e,t){if(1!==e.nodeType)return[];var o=e.ownerDocument.defaultView,n=o.getComputedStyle(e,null);return t?n[t]:n}function o(e){return'HTML'===e.nodeName?e:e.parentNode||e.host}function n(e){if(!e)return document.body;switch(e.nodeName){case'HTML':case'BODY':return e.ownerDocument.body;case'#document':return e.body;}var i=t(e),r=i.overflow,p=i.overflowX,s=i.overflowY;return /(auto|scroll|overlay)/.test(r+s+p)?e:n(o(e))}function r(e){return 11===e?pe:10===e?se:pe||se}function p(e){if(!e)return document.documentElement;for(var o=r(10)?document.body:null,n=e.offsetParent||null;n===o&&e.nextElementSibling;)n=(e=e.nextElementSibling).offsetParent;var i=n&&n.nodeName;return i&&'BODY'!==i&&'HTML'!==i?-1!==['TH','TD','TABLE'].indexOf(n.nodeName)&&'static'===t(n,'position')?p(n):n:e?e.ownerDocument.documentElement:document.documentElement}function s(e){var t=e.nodeName;return'BODY'!==t&&('HTML'===t||p(e.firstElementChild)===e)}function d(e){return null===e.parentNode?e:d(e.parentNode)}function a(e,t){if(!e||!e.nodeType||!t||!t.nodeType)return document.documentElement;var o=e.compareDocumentPosition(t)&Node.DOCUMENT_POSITION_FOLLOWING,n=o?e:t,i=o?t:e,r=document.createRange();r.setStart(n,0),r.setEnd(i,0);var l=r.commonAncestorContainer;if(e!==l&&t!==l||n.contains(i))return s(l)?l:p(l);var f=d(e);return f.host?a(f.host,t):a(e,d(t).host)}function l(e){var t=1=o.clientWidth&&n>=o.clientHeight}),l=0a[e]&&!t.escapeWithReference&&(n=Q(f[o],a[e]-('right'===e?f.width:f.height))),le({},o,n)}};return l.forEach(function(e){var t=-1===['left','top'].indexOf(e)?'secondary':'primary';f=fe({},f,m[t](e))}),e.offsets.popper=f,e},priority:['left','right','top','bottom'],padding:5,boundariesElement:'scrollParent'},keepTogether:{order:400,enabled:!0,fn:function(e){var t=e.offsets,o=t.popper,n=t.reference,i=e.placement.split('-')[0],r=Z,p=-1!==['top','bottom'].indexOf(i),s=p?'right':'bottom',d=p?'left':'top',a=p?'width':'height';return o[s]r(n[s])&&(e.offsets.popper[d]=r(n[s])),e}},arrow:{order:500,enabled:!0,fn:function(e,o){var n;if(!K(e.instance.modifiers,'arrow','keepTogether'))return e;var i=o.element;if('string'==typeof i){if(i=e.instance.popper.querySelector(i),!i)return e;}else if(!e.instance.popper.contains(i))return console.warn('WARNING: `arrow.element` must be child of its popper element!'),e;var r=e.placement.split('-')[0],p=e.offsets,s=p.popper,d=p.reference,a=-1!==['left','right'].indexOf(r),l=a?'height':'width',f=a?'Top':'Left',m=f.toLowerCase(),h=a?'left':'top',c=a?'bottom':'right',u=S(i)[l];d[c]-us[c]&&(e.offsets.popper[m]+=d[m]+u-s[c]),e.offsets.popper=g(e.offsets.popper);var b=d[m]+d[l]/2-u/2,w=t(e.instance.popper),y=parseFloat(w['margin'+f],10),E=parseFloat(w['border'+f+'Width'],10),v=b-e.offsets.popper[m]-y-E;return v=ee(Q(s[l]-u,v),0),e.arrowElement=i,e.offsets.arrow=(n={},le(n,m,$(v)),le(n,h,''),n),e},element:'[x-arrow]'},flip:{order:600,enabled:!0,fn:function(e,t){if(W(e.instance.modifiers,'inner'))return e;if(e.flipped&&e.placement===e.originalPlacement)return e;var o=v(e.instance.popper,e.instance.reference,t.padding,t.boundariesElement,e.positionFixed),n=e.placement.split('-')[0],i=T(n),r=e.placement.split('-')[1]||'',p=[];switch(t.behavior){case ge.FLIP:p=[n,i];break;case ge.CLOCKWISE:p=G(n);break;case ge.COUNTERCLOCKWISE:p=G(n,!0);break;default:p=t.behavior;}return p.forEach(function(s,d){if(n!==s||p.length===d+1)return e;n=e.placement.split('-')[0],i=T(n);var a=e.offsets.popper,l=e.offsets.reference,f=Z,m='left'===n&&f(a.right)>f(l.left)||'right'===n&&f(a.left)f(l.top)||'bottom'===n&&f(a.top)f(o.right),g=f(a.top)f(o.bottom),b='left'===n&&h||'right'===n&&c||'top'===n&&g||'bottom'===n&&u,w=-1!==['top','bottom'].indexOf(n),y=!!t.flipVariations&&(w&&'start'===r&&h||w&&'end'===r&&c||!w&&'start'===r&&g||!w&&'end'===r&&u);(m||b||y)&&(e.flipped=!0,(m||b)&&(n=p[d+1]),y&&(r=z(r)),e.placement=n+(r?'-'+r:''),e.offsets.popper=fe({},e.offsets.popper,D(e.instance.popper,e.offsets.reference,e.placement)),e=P(e.instance.modifiers,e,'flip'))}),e},behavior:'flip',padding:5,boundariesElement:'viewport'},inner:{order:700,enabled:!1,fn:function(e){var t=e.placement,o=t.split('-')[0],n=e.offsets,i=n.popper,r=n.reference,p=-1!==['left','right'].indexOf(o),s=-1===['top','left'].indexOf(o);return i[p?'left':'top']=r[o]-(s?i[p?'width':'height']:0),e.placement=T(t),e.offsets.popper=g(i),e}},hide:{order:800,enabled:!0,fn:function(e){if(!K(e.instance.modifiers,'hide','preventOverflow'))return e;var t=e.offsets.reference,o=C(e.instance.modifiers,function(e){return'preventOverflow'===e.name}).boundaries;if(t.bottomo.right||t.top>o.bottom||t.rightwindow.devicePixelRatio||!me),c='bottom'===o?'top':'bottom',g='right'===n?'left':'right',b=H('transform');if(d='bottom'==c?'HTML'===l.nodeName?-l.clientHeight+h.bottom:-f.height+h.bottom:h.top,s='right'==g?'HTML'===l.nodeName?-l.clientWidth+h.right:-f.width+h.right:h.left,a&&b)m[b]='translate3d('+s+'px, '+d+'px, 0)',m[c]=0,m[g]=0,m.willChange='transform';else{var w='bottom'==c?-1:1,y='right'==g?-1:1;m[c]=d*w,m[g]=s*y,m.willChange=c+', '+g}var E={"x-placement":e.placement};return e.attributes=fe({},E,e.attributes),e.styles=fe({},m,e.styles),e.arrowStyles=fe({},e.offsets.arrow,e.arrowStyles),e},gpuAcceleration:!0,x:'bottom',y:'right'},applyStyle:{order:900,enabled:!0,fn:function(e){return j(e.instance.popper,e.styles),V(e.instance.popper,e.attributes),e.arrowElement&&Object.keys(e.arrowStyles).length&&j(e.arrowElement,e.arrowStyles),e},onLoad:function(e,t,o,n,i){var r=L(i,t,e,o.positionFixed),p=O(o.placement,r,t,e,o.modifiers.flip.boundariesElement,o.modifiers.flip.padding);return t.setAttribute('x-placement',p),j(t,{position:o.positionFixed?'fixed':'absolute'}),o},gpuAcceleration:void 0}}},ue}); 5 | //# sourceMappingURL=popper.min.js.map 6 | -------------------------------------------------------------------------------- /assets/js/run_prettify.js: -------------------------------------------------------------------------------- 1 | !function(){/* 2 | 3 | Copyright (C) 2013 Google Inc. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | 17 | Copyright (C) 2006 Google Inc. 18 | 19 | Licensed under the Apache License, Version 2.0 (the "License"); 20 | you may not use this file except in compliance with the License. 21 | You may obtain a copy of the License at 22 | 23 | http://www.apache.org/licenses/LICENSE-2.0 24 | 25 | Unless required by applicable law or agreed to in writing, software 26 | distributed under the License is distributed on an "AS IS" BASIS, 27 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 28 | See the License for the specific language governing permissions and 29 | limitations under the License. 30 | */ 31 | (function(){function aa(g){function r(){try{L.doScroll("left")}catch(ba){k.setTimeout(r,50);return}x("poll")}function x(r){if("readystatechange"!=r.type||"complete"==z.readyState)("load"==r.type?k:z)[B](n+r.type,x,!1),!l&&(l=!0)&&g.call(k,r.type||r)}var X=z.addEventListener,l=!1,E=!0,v=X?"addEventListener":"attachEvent",B=X?"removeEventListener":"detachEvent",n=X?"":"on";if("complete"==z.readyState)g.call(k,"lazy");else{if(z.createEventObject&&L.doScroll){try{E=!k.frameElement}catch(ba){}E&&r()}z[v](n+ 32 | "DOMContentLoaded",x,!1);z[v](n+"readystatechange",x,!1);k[v](n+"load",x,!1)}}function T(){U&&aa(function(){var g=M.length;ca(g?function(){for(var r=0;r=c?parseInt(e.substring(1),8):"u"===c||"x"===c?parseInt(e.substring(2),16):e.charCodeAt(1)}function f(e){if(32>e)return(16>e?"\\x0":"\\x")+e.toString(16);e=String.fromCharCode(e); 36 | return"\\"===e||"-"===e||"]"===e||"^"===e?"\\"+e:e}function c(e){var c=e.substring(1,e.length-1).match(RegExp("\\\\u[0-9A-Fa-f]{4}|\\\\x[0-9A-Fa-f]{2}|\\\\[0-3][0-7]{0,2}|\\\\[0-7]{1,2}|\\\\[\\s\\S]|-|[^-\\\\]","g"));e=[];var a="^"===c[0],b=["["];a&&b.push("^");for(var a=a?1:0,h=c.length;ap||122p||90p||122m[0]&&(m[1]+1>m[0]&&b.push("-"),b.push(f(m[1])));b.push("]");return b.join("")}function g(e){for(var a=e.source.match(RegExp("(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)", 38 | "g")),b=a.length,d=[],h=0,m=0;h/,null])):d.push(["com",/^#[^\r\n]*/,null,"#"]));a.cStyleComments&&(f.push(["com",/^\/\/[^\r\n]*/,null]),f.push(["com",/^\/\*[\s\S]*?(?:\*\/|$)/, 45 | null]));if(c=a.regexLiterals){var g=(c=1|\\/=?|::?|<>?>?=?|,|;|\\?|@|\\[|~|{|\\^\\^?=?|\\|\\|?=?|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*("+("/(?=[^/*"+c+"])(?:[^/\\x5B\\x5C"+c+"]|\\x5C"+g+"|\\x5B(?:[^\\x5C\\x5D"+c+"]|\\x5C"+g+")*(?:\\x5D|$))+/")+")")])}(c=a.types)&&f.push(["typ",c]);c=(""+a.keywords).replace(/^ | $/g,"");c.length&&f.push(["kwd", 46 | new RegExp("^(?:"+c.replace(/[\s,]+/g,"|")+")\\b"),null]);d.push(["pln",/^\s+/,null," \r\n\t\u00a0"]);c="^.[^\\s\\w.$@'\"`/\\\\]*";a.regexLiterals&&(c+="(?!s*/)");f.push(["lit",/^@[a-z_$][a-z_$@0-9]*/i,null],["typ",/^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/,null],["pln",/^[a-z_$][a-z_$@0-9]*/i,null],["lit",/^(?:0x[a-f0-9]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+\-]?\d+)?)[a-z]*/i,null,"0123456789"],["pln",/^\\[\s\S]?/,null],["pun",new RegExp(c),null]);return E(d,f)}function B(a,d,f){function c(a){var b= 47 | a.nodeType;if(1==b&&!r.test(a.className))if("br"===a.nodeName.toLowerCase())g(a),a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)c(a);else if((3==b||4==b)&&f){var e=a.nodeValue,d=e.match(n);d&&(b=e.substring(0,d.index),a.nodeValue=b,(e=e.substring(d.index+d[0].length))&&a.parentNode.insertBefore(q.createTextNode(e),a.nextSibling),g(a),b||a.parentNode.removeChild(a))}}function g(a){function c(a,b){var e=b?a.cloneNode(!1):a,p=a.parentNode;if(p){var p=c(p,1),d=a.nextSibling; 48 | p.appendChild(e);for(var f=d;f;f=d)d=f.nextSibling,p.appendChild(f)}return e}for(;!a.nextSibling;)if(a=a.parentNode,!a)return;a=c(a.nextSibling,0);for(var e;(e=a.parentNode)&&1===e.nodeType;)a=e;b.push(a)}for(var r=/(?:^|\s)nocode(?:\s|$)/,n=/\r\n?|\n/,q=a.ownerDocument,k=q.createElement("li");a.firstChild;)k.appendChild(a.firstChild);for(var b=[k],t=0;t=+g[1],d=/\n/g,r=a.a,k=r.length,f=0,q=a.c,n=q.length,c=0,b=a.g,t=b.length,v=0;b[t]=k;var u,e;for(e=u=0;e=m&&(c+=2);f>=p&&(v+=2)}}finally{h&&(h.style.display=a)}}catch(y){Q.console&&console.log(y&&y.stack||y)}}var Q="undefined"!==typeof window?window:{},J=["break,continue,do,else,for,if,return,while"],K=[[J,"auto,case,char,const,default,double,enum,extern,float,goto,inline,int,long,register,restrict,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"], 52 | "catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],R=[K,"alignas,alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,delegate,dynamic_cast,explicit,export,friend,generic,late_check,mutable,namespace,noexcept,noreturn,nullptr,property,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],L=[K,"abstract,assert,boolean,byte,extends,finally,final,implements,import,instanceof,interface,null,native,package,strictfp,super,synchronized,throws,transient"], 53 | M=[K,"abstract,add,alias,as,ascending,async,await,base,bool,by,byte,checked,decimal,delegate,descending,dynamic,event,finally,fixed,foreach,from,get,global,group,implicit,in,interface,internal,into,is,join,let,lock,null,object,out,override,orderby,params,partial,readonly,ref,remove,sbyte,sealed,select,set,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,value,var,virtual,where,yield"],K=[K,"abstract,async,await,constructor,debugger,enum,eval,export,from,function,get,import,implements,instanceof,interface,let,null,of,set,undefined,var,with,yield,Infinity,NaN"], 54 | N=[J,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"],O=[J,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],J=[J,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],P=/^(DIR|FILE|array|vector|(de|priority_)?queue|(forward_)?list|stack|(const_)?(reverse_)?iterator|(unordered_)?(multi)?(set|map)|bitset|u?(int|float)\d*)\b/, 55 | S=/\S/,T=v({keywords:[R,M,L,K,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",N,O,J],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),V={};n(T,["default-code"]);n(E([],[["pln",/^[^]*(?:>|$)/],["com",/^<\!--[\s\S]*?(?:-\->|$)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-", 56 | /^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),"default-markup htm html mxml xhtml xml xsl".split(" "));n(E([["pln",/^[\s]+/,null," \t\r\n"],["atv",/^(?:\"[^\"]*\"?|\'[^\']*\'?)/,null,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/], 57 | ["pun",/^[=<>\/]+/],["lang-js",/^on\w+\s*=\s*\"([^\"]+)\"/i],["lang-js",/^on\w+\s*=\s*\'([^\']+)\'/i],["lang-js",/^on\w+\s*=\s*([^\"\'>\s]+)/i],["lang-css",/^style\s*=\s*\"([^\"]+)\"/i],["lang-css",/^style\s*=\s*\'([^\']+)\'/i],["lang-css",/^style\s*=\s*([^\"\'>\s]+)/i]]),["in.tag"]);n(E([],[["atv",/^[\s\S]+/]]),["uq.val"]);n(v({keywords:R,hashComments:!0,cStyleComments:!0,types:P}),"c cc cpp cxx cyc m".split(" "));n(v({keywords:"null,true,false"}),["json"]);n(v({keywords:M,hashComments:!0,cStyleComments:!0, 58 | verbatimStrings:!0,types:P}),["cs"]);n(v({keywords:L,cStyleComments:!0}),["java"]);n(v({keywords:J,hashComments:!0,multiLineStrings:!0}),["bash","bsh","csh","sh"]);n(v({keywords:N,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}),["cv","py","python"]);n(v({keywords:"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",hashComments:!0,multiLineStrings:!0,regexLiterals:2}), 59 | ["perl","pl","pm"]);n(v({keywords:O,hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb","ruby"]);n(v({keywords:K,cStyleComments:!0,regexLiterals:!0}),["javascript","js","ts","typescript"]);n(v({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,throw,true,try,unless,until,when,while,yes",hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),["coffee"]);n(E([],[["str",/^[\s\S]+/]]), 60 | ["regex"]);var U=Q.PR={createSimpleLexer:E,registerLangHandler:n,sourceDecorator:v,PR_ATTRIB_NAME:"atn",PR_ATTRIB_VALUE:"atv",PR_COMMENT:"com",PR_DECLARATION:"dec",PR_KEYWORD:"kwd",PR_LITERAL:"lit",PR_NOCODE:"nocode",PR_PLAIN:"pln",PR_PUNCTUATION:"pun",PR_SOURCE:"src",PR_STRING:"str",PR_TAG:"tag",PR_TYPE:"typ",prettyPrintOne:function(a,d,f){f=f||!1;d=d||null;var c=document.createElement("div");c.innerHTML="
"+a+"
";c=c.firstChild;f&&B(c,f,!0);H({j:d,m:f,h:c,l:1,a:null,i:null,c:null,g:null}); 61 | return c.innerHTML},prettyPrint:g=function(a,d){function f(){for(var c=Q.PR_SHOULD_USE_CONTINUATION?b.now()+250:Infinity;t { 19 | if (err) return res.sendStatus(403); 20 | Users.findOne({attributes: ['id', 'username', 'email', 'orgname', 'apiToken', 'totpSecret', 'profilePic'], where: {username: user.username}}) 21 | .then((queryResult) => { 22 | if (queryResult == null) { 23 | res.clearCookie('authToken', ''); 24 | res.redirect('/login'); 25 | } else { 26 | req.user = queryResult; 27 | next(); 28 | } 29 | }); 30 | }); 31 | } 32 | 33 | const register_get = (req, res) => { 34 | res.render('register.ejs'); 35 | }; 36 | 37 | function gift_crypto(max, min) { 38 | return (Math.random() * (max - min) + min).toFixed(8); 39 | } 40 | 41 | const register_post = (req, res) => { 42 | const username = req.body.username; 43 | const email = req.body.email; 44 | const password = req.body.password; 45 | if (!emailvalidator.validate(email)) { 46 | res.send(res.status(400).send('Invalid email')); 47 | } 48 | Users.findAll({where: {username: username}}) 49 | .then((count) => { 50 | if (count.length != 0) { 51 | res.status(403).send('User already registerd!'); 52 | } else { 53 | if (username !== '' & password !== '' & email !== '') { 54 | const apiToken = crypto.randomBytes(20).toString('hex'); 55 | Users.create({username: username, email: email, password: md5(password), orgname: '', apiToken: apiToken, totpSecret: ''}); 56 | Org.create({orgname: '', owner: username}); 57 | Wallet.create({username: username, BTC: gift_crypto(0.0025, 0.001), ETH: gift_crypto(0.5, 0.1)}); 58 | const jwt_token = generateAccessToken(username, email); 59 | res.cookie('authToken', jwt_token); 60 | res.send(jwt_token); 61 | } else { 62 | res.status(400).send('username/password/email can not be null'); 63 | } 64 | } 65 | }); 66 | }; 67 | 68 | const logout_get = (req, res) => { 69 | res.clearCookie('authToken', ''); 70 | res.redirect('/login'); 71 | }; 72 | 73 | const login_get = (req, res) => { 74 | res.render('login'); 75 | }; 76 | 77 | const login_post = (req, res) => { 78 | const username = req.body.username; 79 | const password = req.body.password; 80 | if (username !== '' & password !== '') { 81 | Users.findOne({where: {username: username, password: md5(password)}}) 82 | .then((user) => { 83 | if (user) { 84 | const jwt_token = generateAccessToken(username, user.email); 85 | if (user.totpSecret != '') { 86 | res.cookie('authToken', jwt_token); 87 | res.status(200).send('/totp-verification'); 88 | } else { 89 | res.cookie('authToken', jwt_token); 90 | res.status(200).send('/'); 91 | } 92 | } else { 93 | res.status(403).send('Invalid username/password.'); 94 | } 95 | }); 96 | } else { 97 | res.status(400).send(); 98 | } 99 | }; 100 | 101 | module.exports = { 102 | register_get, 103 | register_post, 104 | authenticateToken, 105 | logout_get, 106 | login_get, 107 | login_post, 108 | }; 109 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | mysqldb: 3 | image: mysql:8.0 4 | container_name: mysqldb 5 | command: --default-authentication-plugin=mysql_native_password 6 | restart: unless-stopped 7 | volumes: 8 | - ./models/init.sql:/docker-entrypoint-initdb.d/0_init.sql 9 | ports: 10 | - ${DB_PORT}:${DB_PORT} 11 | expose: 12 | - ${DB_PORT} 13 | environment: 14 | MYSQL_DATABASE: ${DB_NAME} 15 | MYSQL_USER: ${DB_USER} 16 | MYSQL_PASSWORD: ${DB_PASS} 17 | MYSQL_ROOT_PASSWORD: ${DB_PASS} 18 | SERVICE_TAGS: prod 19 | SERVICE_NAME: mysqldb 20 | networks: 21 | - internalnet 22 | 23 | mongodb: 24 | image: mongo 25 | container_name: mongodb 26 | ports: 27 | - 27018:27017 28 | expose: 29 | - 27018 30 | environment: 31 | - MONGO_INITDB_ROOT_USERNAME=vuln_nodejs_app 32 | - MONGO_INITDB_ROOT_PASSWORD=supersecret 33 | networks: 34 | - internalnet 35 | 36 | nodeapp: 37 | container_name: vuln_nodejs_app 38 | build: . 39 | image: payatu/vuln_nodejs_app 40 | volumes: 41 | - .:/code 42 | ports: 43 | - ${HOST_PORT}:${HOST_PORT} 44 | expose: 45 | - ${HOST_PORT} 46 | environment: 47 | HOST_PORT: ${HOST_PORT} 48 | DB_NAME: ${DB_NAME} 49 | DB_USER: ${DB_USER} 50 | DB_PASS: ${DB_PASS} 51 | DB_HOST: mysqldb 52 | DB_PORT: ${DB_PORT} 53 | SERVICE_TAGS: prod 54 | SERVICE_NAME: nodeappservice 55 | JWT_SECRET: ${JWT_SECRET} 56 | MONGODB_SERVER: mongodb 57 | MONGODB_ADMINUSERNAME: vuln_nodejs_app 58 | MONGODB_ADMINPASSWORD: supersecret 59 | depends_on: 60 | - mysqldb 61 | - mongodb 62 | networks: 63 | - internalnet 64 | 65 | networks: 66 | internalnet: 67 | driver: bridge -------------------------------------------------------------------------------- /models/db.js: -------------------------------------------------------------------------------- 1 | const Sequelize = require('sequelize'); 2 | const crypto = require('crypto'); 3 | require("dotenv").config(); 4 | 5 | 6 | const sequelize = new Sequelize( 7 | process.env.DB_NAME, 8 | process.env.DB_USER, 9 | process.env.DB_PASS, { 10 | dialect: 'mysql', 11 | dialectOptions: { 12 | host: process.env.DB_HOST 13 | } 14 | }) 15 | 16 | sequelize 17 | .authenticate() 18 | .then(() => { 19 | console.log('Connected to database!') 20 | }) 21 | .catch((err) => { 22 | console.log(err) 23 | console.log('Unable to connect to database!') 24 | }); 25 | 26 | const Train = sequelize.define('trains', { 27 | 'from_stnt': Sequelize.STRING, 28 | 'to_stnt': Sequelize.STRING, 29 | 'ntrains': Sequelize.INTEGER 30 | }); 31 | 32 | const Users = sequelize.define('users', { 33 | 'username': Sequelize.STRING, 34 | 'email': Sequelize.STRING, 35 | 'password': Sequelize.STRING, 36 | 'orgname': Sequelize.STRING, 37 | 'apiToken': Sequelize.STRING, 38 | 'totpSecret': Sequelize.STRING, 39 | "profilePic": { 40 | type: Sequelize.STRING, 41 | defaultValue: "default.png" 42 | } 43 | }) 44 | 45 | const Org = sequelize.define('orgs', { 46 | "orgname": Sequelize.STRING, 47 | "owner": Sequelize.STRING 48 | }) 49 | 50 | const Notes = sequelize.define('notes', { 51 | "userid": Sequelize.INTEGER, 52 | "username": Sequelize.STRING, 53 | "noteTitle": Sequelize.STRING, 54 | "noteBody": Sequelize.STRING 55 | }) 56 | 57 | const Wallet = sequelize.define('wallets',{ 58 | "username": Sequelize.STRING, 59 | "BTC": Sequelize.FLOAT, 60 | "ETH": Sequelize.FLOAT 61 | }) 62 | 63 | sequelize.sync({ force: true }) 64 | .then(() => { 65 | Train.bulkCreate([ 66 | { from_stnt: 'Delhi', to_stnt: 'Kolkata', ntrains: 7 }, 67 | { from_stnt: 'Delhi', to_stnt: 'Mumbai', ntrains: 9 }, 68 | { from_stnt: 'Delhi', to_stnt: 'Lucknow', ntrains: 6 }, 69 | { from_stnt: 'Delhi', to_stnt: 'Ahmedabad', ntrains: 5 }, 70 | { from_stnt: 'Delhi', to_stnt: 'Chennai', ntrains: 3 } 71 | ]); 72 | Users.create({ 73 | username: 'vulnlabAdmin', 74 | email: 'vulnlabAdmin@vuln.js', 75 | password: 'SuperSecurePassword', 76 | orgname: '', 77 | apiToken:"YouHaveCompletedTheExcercise", 78 | totpSecret: '', 79 | profilePic: 'default.png' } 80 | ); 81 | Notes.create({ 82 | userid:'1', 83 | username: 'vulnlabAdmin', 84 | noteTitle: 'ThisIsAdminNote', 85 | noteBody: 'SuperSecretNote' 86 | }); 87 | Wallet.create({ 88 | username: "vulnlabAdmin", 89 | BTC: "0.00245", 90 | ETH: "0.5" 91 | }) 92 | }) 93 | .catch((err) => { 94 | console.log(err) 95 | console.log('Unable to connect to database!') 96 | }); 97 | 98 | module.exports = { 99 | Train, 100 | Users, 101 | Notes, 102 | Org, 103 | Wallet 104 | } 105 | -------------------------------------------------------------------------------- /models/graphql-schema.js: -------------------------------------------------------------------------------- 1 | const {buildSchema} = require('graphql') 2 | 3 | var schema = buildSchema(` 4 | type Query { 5 | user(username: String!): User 6 | listUsers: [Users] 7 | showProfile(userid: Int!): User 8 | } 9 | 10 | type User { 11 | username: String 12 | email: String 13 | } 14 | 15 | type Users { 16 | username: String 17 | email: String 18 | } 19 | 20 | type Mutation { 21 | updateProfile(username: String, email: String, password: String!): String 22 | } 23 | `) 24 | 25 | module.exports = { 26 | schema 27 | } -------------------------------------------------------------------------------- /models/init.sql: -------------------------------------------------------------------------------- 1 | CREATE DATABASE IF NOT EXISTS vuln_nodejs_lab; 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "cookie": "^0.4.1", 4 | "cookie-parser": "^1.4.6", 5 | "dotenv": "^14.2.0", 6 | "ejs": "^3.1.6", 7 | "email-validator": "^2.0.4", 8 | "express": "^4.17.2", 9 | "express-fileupload": "^1.3.1", 10 | "express-graphql": "^0.12.0", 11 | "html-pdf-node": "^1.0.8", 12 | "jsonwebtoken": "^8.5.1", 13 | "libxmljs": "^0.19.7", 14 | "md5": "^2.3.0", 15 | "mongodb": "^4.3.1", 16 | "mysql2": "^2.3.3", 17 | "node-2fa": "^2.0.3", 18 | "node-serialize": "^0.0.4", 19 | "pg": "^8.7.1", 20 | "request": "^2.88.2", 21 | "sequelize": "^6.12.5", 22 | "socket.io": "2.3.0", 23 | "x-xss-protection": "^2.0.0" 24 | }, 25 | "scripts": { 26 | "start": "nodemon server.js", 27 | "build": "cd vuln_react_app && npm install && npm run build" 28 | }, 29 | "name": "vuln-nodejs-app", 30 | "version": "1.0.0", 31 | "description": "Vulnerable node.js application", 32 | "main": "server.js", 33 | "author": "", 34 | "license": "ISC", 35 | "devDependencies": { 36 | "eslint": "^8.5.0", 37 | "eslint-config-airbnb-base": "^15.0.0", 38 | "eslint-config-google": "^0.14.0", 39 | "eslint-plugin-import": "^2.25.3" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /routes/app.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const vuln_controller = require("../controllers/vuln_controller"); 3 | const router = express.Router(); 4 | const auth_controller = require('../controllers/auth_controller'); 5 | const { authenticateToken } = require('../controllers/auth_controller'); 6 | const { schema } = require('../models/graphql-schema') 7 | const { graphqlHTTP } = require('express-graphql') 8 | 9 | router.get('/', authenticateToken, vuln_controller.app_index); 10 | 11 | router.route('/register') 12 | .get(auth_controller.register_get) 13 | .post(auth_controller.register_post); 14 | 15 | router.get('/xss', authenticateToken, vuln_controller.xss_lab); 16 | 17 | router.get('/ssti', authenticateToken, vuln_controller.ssti); 18 | 19 | router.route('/ping') 20 | .get(authenticateToken, vuln_controller.ping_get) 21 | .post(authenticateToken, vuln_controller.ping_post); 22 | 23 | router.get('/sqli', authenticateToken, vuln_controller.sqli_get); 24 | 25 | router.get('/xxe', authenticateToken, vuln_controller.xxe_get); 26 | 27 | router.post('/comment', authenticateToken, vuln_controller.xxe_comment); 28 | 29 | router.get('/auth', vuln_controller.auth_get); 30 | 31 | router.get('/dashboard', vuln_controller.dashboard_get); 32 | 33 | router.get('/userinfo', authenticateToken, vuln_controller.userinfo_get); 34 | 35 | router.get('/sitetoken/:username', authenticateToken, vuln_controller.sitetoken_get); 36 | 37 | router.get('/sqli/:from-:to', authenticateToken, vuln_controller.sqli_check_train_get); 38 | 39 | router.get('/sqli-fixed/:from-:to', authenticateToken, vuln_controller.fixed_sqli_check_train_get); 40 | 41 | router.get('/sqli-fixed/', authenticateToken, vuln_controller.sqli_fixed_get) 42 | 43 | router.get('/logout', authenticateToken, auth_controller.logout_get); 44 | 45 | router.get('/deserialization', authenticateToken, vuln_controller.deserialization_get); 46 | 47 | router.post('/save-preference', authenticateToken, vuln_controller.save_preference_post); 48 | 49 | router.get('/jwt1', authenticateToken, vuln_controller.jwt1_get); 50 | 51 | router.get('/jwt1/apiKey', authenticateToken, vuln_controller.jwt1ApiKey); 52 | 53 | router.route('/notes') 54 | .get(authenticateToken, vuln_controller.notes_get) 55 | .post(authenticateToken, vuln_controller.notes_post); 56 | 57 | router.get('/notes/user/:userid', authenticateToken, vuln_controller.userNotes_get); 58 | 59 | router.route('/login') 60 | .get(auth_controller.login_get) 61 | .post(auth_controller.login_post); 62 | 63 | router.get('/ticket', authenticateToken, vuln_controller.ticket_get); 64 | 65 | router.get('/ticket/booking', authenticateToken, vuln_controller.ticket_booking_get); 66 | 67 | router.get('/ticket/generate_ticket', vuln_controller.generate_ticket_get); 68 | 69 | router.get('/user-edit', authenticateToken, vuln_controller.user_edit_get); 70 | 71 | router.route('/edit-password') 72 | .get(authenticateToken, vuln_controller.edit_password_get) 73 | .post(authenticateToken, vuln_controller.edit_password_post) 74 | 75 | router.route('/organization') 76 | .get(authenticateToken, vuln_controller.organization_get) 77 | .post(authenticateToken, vuln_controller.organization_post); 78 | 79 | router.route('/organization/add-user') 80 | .get(authenticateToken, vuln_controller.add_user_get) 81 | .post(authenticateToken, vuln_controller.add_user_post); 82 | 83 | router.get('/organization/myorg', authenticateToken, vuln_controller.myorg_get); 84 | 85 | router.get('/organization/myorg/users', authenticateToken, vuln_controller.myorg_users_get); 86 | 87 | router.get('/api-token', authenticateToken, vuln_controller.apitoken_get); 88 | 89 | router.get('/api-token/show', authenticateToken, vuln_controller.apitokenShow_get); 90 | 91 | router.get('/cors-api-token', authenticateToken, vuln_controller.cors_api_token_get); 92 | 93 | router.route('/cors-csrf-edit-password') 94 | .get(authenticateToken, vuln_controller.cors_csrf_edit_password_get) 95 | .post(authenticateToken, vuln_controller.cors_csrf_edit_password_post) 96 | .options(vuln_controller.cors_csrf_edit_password_option); 97 | 98 | router.route('/setup-totp') 99 | .get(authenticateToken, vuln_controller.totp_setup_get) 100 | .post(authenticateToken, vuln_controller.totp_setup_post); 101 | 102 | router.route('/totp-verification') 103 | .get(authenticateToken, vuln_controller.login_totp_verification_get) 104 | .post(authenticateToken, vuln_controller.login_totp_verification_post); 105 | 106 | router.post('/disable-totp', authenticateToken, vuln_controller.totp_disable_post); 107 | 108 | router.get('/cross-site-websocket-hijacking', authenticateToken, vuln_controller.websocket_hijacking_get); 109 | 110 | router.get('/websocket-xss', authenticateToken, vuln_controller.websocket_xss_get); 111 | 112 | router.route('/react*') 113 | .get(authenticateToken, vuln_controller.react_xss_get) 114 | .post(vuln_controller.react_xss_post); 115 | 116 | router.route('/mongodb-notes') 117 | .get(authenticateToken, vuln_controller.mongodb_notes_get) 118 | .post(authenticateToken, vuln_controller.mongodb_save_notes_post) 119 | 120 | router.route('/mongodb-notes/show-notes') 121 | .post(authenticateToken, vuln_controller.mongodb_show_notes_post); 122 | 123 | // GraphQL rotuer 124 | router.use('/graphql', authenticateToken, graphqlHTTP({ 125 | schema: schema, 126 | rootValue: vuln_controller.graphqlroot 127 | })) 128 | 129 | router.get('/graphql-user-profile', authenticateToken, vuln_controller.graphql_user_profile_get); 130 | 131 | router.get('/graphql-information-disclosure', authenticateToken, vuln_controller.graphql_information_disclosure_get); 132 | 133 | router.get('/graphql-update-profile', authenticateToken, vuln_controller.graphql_update_profile_get); 134 | 135 | router.get('/graphql-idor-show-profile', authenticateToken, vuln_controller.graphql_idor_get); 136 | 137 | router.route('/svg-xss') 138 | .get(authenticateToken, vuln_controller.svg_xss_get) 139 | .post(authenticateToken, vuln_controller.svg_xss_fileupload_post); 140 | 141 | router.get('/jsonp-injection', authenticateToken, vuln_controller.jsonp_injection_get); 142 | 143 | router.get('/jsonp-injection/wallet-usd-balance', authenticateToken, vuln_controller.jsonp_wallet_get); 144 | 145 | router.get('/nosql-js-injection', authenticateToken, vuln_controller.nosql_javascript_injection_get); 146 | 147 | router.post('/unlock-secret', authenticateToken, vuln_controller.secret_post); 148 | 149 | module.exports = router; -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const app = express(); 3 | const router = require('./routes/app'); 4 | const cookieParser = require('cookie-parser'); 5 | const jwt = require('jsonwebtoken'); 6 | const {Users, Wallet} = require('./models/db'); 7 | const request = require('request'); 8 | const path = require('path'); 9 | const fileUpload = require('express-fileupload'); 10 | 11 | require('dotenv').config(); 12 | 13 | app.use(cookieParser()); 14 | 15 | app.use(express.urlencoded({extended: true})); 16 | 17 | app.use(express.json()); 18 | 19 | app.use(fileUpload()); 20 | 21 | app.use(express.static('./uploads')); 22 | 23 | app.use(router); 24 | 25 | app.use(express.static('./assets')); 26 | 27 | app.use(express.static(path.resolve(__dirname, './vuln_react_app/build'))); 28 | 29 | app.set('view engine', 'ejs'); 30 | 31 | const server = app.listen(process.env.HOST_PORT, function() { 32 | console.log('Listening on port ', process.env.HOST_PORT); 33 | }); 34 | 35 | const io = require('socket.io')(server); 36 | io.use((socket, next) => { 37 | const cookies = cookie.parse(socket.request.headers.cookie || ''); 38 | const jwt_token = cookies.authToken; 39 | if (jwt_token) { 40 | jwt.verify(jwt_token, process.env.JWT_SECRET, (err, user) => { 41 | if (err) return next(new Error('Authentication error')); 42 | Users.findOne({attributes: ['username'], where: {username: user.username}}) 43 | .then((queryResult) => { 44 | if (queryResult == null) return next(new Error('User does not exist in database!')); 45 | socket.user = queryResult; 46 | next(); 47 | }); 48 | }); 49 | } else { 50 | next(new Error('Authentication error')); 51 | } 52 | }) 53 | .on('connection', function(socket) { 54 | console.log('A new client is connected'); 55 | 56 | socket.on('crypto_usd_value', function(message) { 57 | Wallet.findOne({where: {username: socket.user.username}}, {attributes: ['BTC', 'ETH']}) 58 | .then((crypto_balance)=>{ 59 | const bitcoin_quantity = crypto_balance.BTC; 60 | const ethereum_quantity = crypto_balance.ETH; 61 | request('https://api.coingecko.com/api/v3/simple/price?ids=bitcoin%2Cethereum&vs_currencies=usd', {json: true}, (err, res, body)=>{ 62 | if (err) { 63 | return console.log(err); 64 | } 65 | const bitcoin_usd_value = bitcoin_quantity * body.bitcoin.usd + Math.floor(Math.random() * 100); 66 | const ethereum_usd_value = ethereum_quantity * body.ethereum.usd + Math.floor(Math.random() * 100); 67 | const total_usd_value = bitcoin_usd_value + ethereum_usd_value + Math.floor(Math.random() * 100); 68 | socket.emit('crypto_usd_value', {'user': socket.user.username, 'btc': bitcoin_usd_value, 'eth': ethereum_usd_value, 'total': total_usd_value}); 69 | }); 70 | }); 71 | }); 72 | 73 | socket.on('new_message', (data) => { 74 | io.sockets.emit('new_message', {message: data.message, username: socket.user.username, login_user: socket.user.username}); 75 | }); 76 | }); 77 | -------------------------------------------------------------------------------- /solutions/solutions.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/payatu/vuln-nodejs-app/bc3c90920ba4e1c668d954e5027d9ca814d2d5c6/solutions/solutions.pdf -------------------------------------------------------------------------------- /views/add-user.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <%- include ('includes/header.ejs') %> 8 | 9 | 10 | 11 |
12 |
13 |
14 |
15 |
Add a user in your org
16 |


17 | 18 | 20 |
21 |

22 |
23 |
24 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /views/cors-api-token.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <%- include ('includes/header.ejs') %> 8 | 9 | 10 | 11 | <%- include ('includes/navigation.ejs') %> 12 |
13 |

CORS Information Disclosure

14 |
15 |

Your goal is to steal the API token by exploiting CORS.

16 |
17 |
18 |
API Token
19 |

20 |
21 |
22 |

23 |

Vulnerable Code:

24 |

filename: /views/cors-api-token.ejs

25 |
26 |
27 | 
28 |  const cors_api_token_get = (req, res) => {
29 |     const apiToken = req.user.apiToken;
30 |     if (req.get('origin') !== undefined){
31 |         res.header("Access-Control-Allow-Origin", req.get('origin'));
32 |         res.header("Access-Control-Allow-Credentials", 'true');
33 |     }
34 |     res.render('cors-api-token', {apiToken})
35 |  }
36 |                 
37 |
38 | 39 | 40 | -------------------------------------------------------------------------------- /views/cors-edit-password.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <%- include ('includes/header.ejs') %> 8 | 9 | 10 | 11 | <%- include ('includes/navigation.ejs') %> 12 |
13 |

CORS CSRF

14 |
15 |

Your goal is to change user password by exploiting CORS

16 |
17 |
18 |
Change Password

19 |

21 |

23 |

25 |
26 |
27 |
28 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /views/cross-site-websocket-hijacking.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <%- include ('includes/header.ejs') %> 8 | 9 | 10 | 11 | 12 | <%- include ('includes/navigation.ejs') %> 13 |
14 |

Cross-Site WebSocket Hijacking

15 |
16 |

Application is using old version of socket.io to show USD value of user crypto wallet, your goal is to 17 | steal the wallet balance information. 18 |

19 |
20 |

Your Wallet

21 |

22 |

Total Value:


24 | Bitcoin: 26 |
<%= BTC %>
27 | Ethereum: 29 |
<%= ETH %>
30 |

31 |
32 |
33 |

34 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /views/deserialization.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <%- include ('includes/header.ejs') %> 8 | 9 | 10 | 11 | <%- include ('includes/navigation.ejs') %> 12 |
13 |

Vulnerability: Insecure Deserialisation

14 |
15 |

Application is using node-serialize method unserialize to parse the preference cookie, your goal is to acheive the RCE.

16 |
17 |
18 |

Shoe preference

19 |

20 | 21 | 22 | Brand: 23 | 24 | 25 | 26 | Nike 27 | Asics 28 | 29 | 30 | Puma 31 | Reebok 32 | 33 | 34 | Jordan 35 |
36 | 37 | 38 | Color: 39 | 40 | 41 | 42 | Black 43 | Brown 44 | 45 | 46 | Blue 47 | White 48 | 49 | 50 |

51 | 52 |

53 |
54 |
55 |
56 |
Vulnerable Code:
57 |
58 |
59 |
 60 | 
 61 |   var serialize = require('node-serialize');
 62 |   ....  
 63 |   const preference = serialize.unserialize(req.cookies.preference);
 64 | 
 65 | 
66 |
67 | 68 | 108 | 109 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /views/edit_password.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <%- include ('includes/header.ejs') %> 8 | 9 | 10 | 11 |
12 |
13 |
14 |
15 |
Change Password

16 |

17 |

18 |

19 |
20 |
21 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /views/graphql-idor-show-profile.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%- include ('includes/header.ejs') %> 5 | 6 | 7 | 8 | <%- include ('includes/navigation.ejs') %> 9 | 10 |
11 |

Graphql IDOR

12 |
13 |

Application is trusting data supplied from the API client and using 14 | it to show the user details without performing the authorization check, 15 | your goal is to access details of other users by perfrom IDOR. 16 |

17 |
18 |
19 |
20 |
User Profile
21 |

22 |

Username:

23 |

Email:

24 |

25 |
26 |
27 |
28 |
29 |

30 |

Vulnerable Code:

31 |
32 |
33 | 
34 |   # vuln_controller.js
35 |    const graphqlroot = {
36 |      user: graphql_GetUser,
37 |      listUsers: graphql_AllUsers,
38 |      updateProfile: graphql_UpdateProfile,
39 |      showProfile: graphql_ShowProfile
40 |   }
41 | 
42 |   # vuln_controller.js
43 |   async function graphql_ShowProfile(args){
44 |     const userid = args.userid
45 |     const q = "SELECT * FROM users where id='" + userid + "';";
46 |     await con.connect()
47 |     const userdata = await con.promise().query(q)
48 |     return userdata[0][0]
49 | }          
50 |                 
51 |
52 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /views/graphql-information-disclosure.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <%- include ('includes/header.ejs') %> 8 | 9 | 10 | 11 | <%- include ('includes/navigation.ejs') %> 12 | 13 |
14 |

GraphQL Information Disclosure

15 |
16 |

Application is using GraphQL, your goal is to disclose the information of other registered users.

17 |
18 | 25 |
26 |

27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /views/graphql-update-profile.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <%- include ('includes/header.ejs') %> 6 | 7 | 8 | 9 | <%- include ('includes/navigation.ejs') %> 10 | 11 |
12 |

Graphql CSRF

13 |
14 |

Application is using graphql to update user information, your goal is to change information of other users by 15 | perform CSRF attack. 16 |

17 |
18 |
19 |
20 |
User Profile
21 |

22 |

Username:

23 |

Email:

24 |

Password:

25 |

26 |

27 |
28 |
29 |
30 |
31 |

32 |
Reference:
33 |
  • express-graphql
  • 34 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /views/graphql-user-profile.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%- include ('includes/header.ejs') %> 5 | 6 | 7 | 8 | <%- include ('includes/navigation.ejs') %> 9 | 10 |
    11 |

    Graphql SQLI

    12 |
    13 |

    Application is trusting data supplied from the API client and using it in a sql query without proper 14 | sanitization to display user profile, your goal is to perform SQL injection. 15 |

    16 |
    17 |
    18 |
    19 |
    User Profile
    20 |

    21 |

    Username:

    22 |

    Email:

    23 |

    24 |
    25 |
    26 |
    27 |
    28 |

    29 |

    Vulnerable Code:

    30 |
    31 |
    32 | 
    33 |   # vuln_controller.js
    34 |   const graphqlroot = {
    35 |     user: graphql_GetUser,
    36 |     listUsers: graphql_AllUsers,
    37 |     updateProfile: graphql_UpdateProfile,
    38 |     showProfile: graphql_ShowProfile
    39 |   }
    40 |              
    41 |   # vuln_controller.js
    42 |   async function graphqlGetUser(arg){
    43 |     const username = arg.username
    44 |     const q = "SELECT * FROM users where username='" + username + "';";
    45 |     await con.connect()
    46 |     const userdata = await con.promise().query(q)
    47 |     console.log(userdata[0][0])
    48 |     return userdata[0][0]
    49 |   }            
    50 |                 
    51 |
    52 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /views/includes/header.ejs: -------------------------------------------------------------------------------- 1 | Vulnerable NodeJS Application 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /views/includes/navigation.ejs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /views/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%- include ('includes/header.ejs') %> 5 | 6 | 7 | 8 | <%- include ('includes/navigation.ejs') %> 9 |
    10 |
    11 |
    12 |

    Vulnerabilities

    13 |
    14 |
    15 |
    16 |
    17 |
    Command Injection
    18 |

    Application is using exec method to run ping, your goal is to acheive the Remote 19 | Code Execution.

    20 | Exploit -> 21 |
    22 |
    23 |
    24 |
    25 |
    26 |
    27 |
    Insecure Deserialization
    28 |

    Application is using node-serialize method unserialize to parse preference cookie, 29 | your goal is to acheive the RCE.

    30 | 31 | Exploit -> 32 |
    33 |
    34 |
    35 |
    36 |
    37 |
    38 |
    39 |
    40 |
    41 |
    JWT
    42 |

    Application is using weak secret to create JSON web token that can lead to 43 | authentication bypass.

    44 | Exploit -> 45 |
    46 |
    47 |
    48 |
    49 |
    50 |
    51 |
    IDOR
    52 |

    Application has functionality to store notes, your goal is to access notes saved in 53 | vulnlabAdmin account.

    54 | Exploit -> 55 |
    56 |
    57 |
    58 |
    59 |
    60 |
    61 |
    62 |
    63 |
    64 |
    XSS
    65 |

    Three different XSS cases are covered in this exercise depending on the ejs template syntax used to display user supplied input and context.

    66 | Exploit -> 67 |
    68 |
    69 |
    70 |
    71 |
    72 |
    73 |
    SSTI
    74 |

    Application is directly concatenating user supplied input to the template that can lead to SSTI your goal is to steal the database credentials.

    75 |
    76 | Exploit -> 77 |
    78 |
    79 |
    80 |
    81 |
    82 |
    83 |
    84 |
    85 |
    86 |
    SQL Injection
    87 |

    Application is concatenating user supplied input to SQL statement without any 88 | santization.

    89 | Exploit -> 90 |
    91 |
    92 |
    93 |
    94 |
    95 |
    96 |
    97 |
    XXE
    98 |

    Application is using libxmljs to parse xml input but noent flag is set to true which 99 | enables external entities parsing.

    100 | Exploit -> 101 |
    102 |
    103 |
    104 |
    105 |
    106 |
    107 |
    108 |
    109 |
    110 |
    SSRF via PDF generator
    111 |

    Application is using html-pdf-node to generate ticket pdf your goal is to acheive 112 | the SSRF

    113 | Exploit -> 114 |
    115 |
    116 |
    117 |
    118 |
    119 |
    120 |
    postMessage XSS
    121 |

    Application has functionality to update password, which opens a new window and 122 | listens for the post message can you exploit it?

    123 | Exploit -> 124 |
    125 |
    126 |
    127 |
    128 |
    129 |
    130 |
    131 |
    132 |
    133 |
    postMessage CSRF
    134 |

    Application has functionality to add users in organization, your goal is to add 135 | your self in a victim organization by exploiting web messages.

    136 | Exploit -> 137 |
    138 |
    139 |
    140 |
    141 |
    142 |
    143 |
    Information Disclosure using addEventListener
    144 |

    Can you steal the user API token by exploiting Web Message.

    145 |
    146 | Exploit -> 147 |
    148 |
    149 |
    150 |
    151 |
    152 |
    153 |
    154 |
    155 |
    156 |
    CORS Information Disclosure
    157 |

    Steal the user API token by exploiting CORS.


    158 | Exploit -> 159 |
    160 |
    161 |
    162 |
    163 |
    164 |
    165 |
    CORS CSRF
    166 |

    Application has insecure CORS implementation, your goal is to change user password 167 | by exploiting CORS.

    168 | Exploit -> 169 |
    170 |
    171 |
    172 |
    173 |
    174 |
    175 |
    176 |
    177 |
    178 |
    2FA
    179 |

    Application has insecure 2FA implementation.


    180 | Exploit -> 181 |
    182 |
    183 |
    184 |
    185 |
    186 |
    187 |
    Cross-Site WebSocket Hijacking
    188 |

    Application is using old version of socket.io, your goal is to exploit it to 189 | disclose the balance in vicitm user account.

    190 | Exploit -> 191 |
    192 |
    193 |
    194 |
    195 |
    196 |
    197 |
    198 |
    199 |
    200 |
    WebSocket XSS
    201 |

    Application is using websocket for real-time chat feature, But has only implemented 202 | client side input santization.


    203 | Exploit -> 204 |
    205 |
    206 |
    207 |
    208 |
    209 |
    210 |
    ReactJS XSS
    211 |

    Application is using ReactJS which provides by default protection from XSS your 212 | goal is to 213 | perform XSS in ReactJS applications.

    214 |
    215 | Exploit -> 216 |
    217 |
    218 |
    219 |
    220 |
    221 |
    222 |
    223 |
    224 |
    225 |
    React ref-innerHTML XSS
    226 |

    Application is using ref escape hatch with innerHTML 227 | property to update user supplied input.

    Exploit -> 229 |
    230 |
    231 |
    232 |
    233 |
    234 |
    235 |
    NoSQL Injection
    236 |

    Application is using mongodb to handle user notes your goal is to 237 | perform NoSQL injection to access other users notes.

    238 | Exploit -> 239 |
    240 |
    241 |
    242 |
    243 |
    244 |
    245 |
    246 |
    247 |
    248 |
    Graphql Information Disclosure
    249 |

    Application is using GraphQL, your goal is to disclose the information of other registered users.


    Exploit -> 251 |
    252 |
    253 |
    254 |
    255 |
    256 |
    257 |
    Graphql SQLI
    258 |

    Application is trusting data supplied from the API client and using it in a sql query without proper 259 | santization your goal is to perform SQL injection.

    Exploit -> 261 |
    262 |
    263 |
    264 |
    265 |
    266 |
    267 |
    268 |
    269 |
    270 |
    Graphql CSRF
    271 |

    Application is using GraphQL, your goal is to perform CSRF.


    Exploit -> 273 |
    274 |
    275 |
    276 |
    277 |
    278 |
    279 |
    Graphql IDOR
    280 |

    Application is trusting data supplied from the API client and using 281 | it to show the user details without performing the authorization check.

    Exploit -> 283 |
    284 |
    285 |
    286 |
    287 |
    288 |
    289 |
    290 |
    291 |
    292 |
    XSS using SVG file uplaod
    293 |

    Application has a functionality to upload profile picture your goal is to perform XSS.


    Exploit -> 295 |
    296 |
    297 |
    298 |
    299 |
    300 |
    301 |
    JSONP Injection
    302 |

    Application is using JSONP for fetching wallet balance information your goal is to steal wallet information of other users.

    303 |
    304 | Exploit -> 306 |
    307 |
    308 |
    309 |
    310 |
    311 |
    312 |
    313 |
    314 |
    315 |
    NoSQL Javascript Injection
    316 |

    Application has a password protected secret your goal is to access it by using NoSQL injection.


    Exploit -> 318 |
    319 |
    320 |
    321 |
    322 |
    323 |
    324 |
    325 |
    326 | 327 | -------------------------------------------------------------------------------- /views/jsonp-injection.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <%- include ('includes/header.ejs') %> 8 | 9 | 10 | 11 | <%- include ('includes/navigation.ejs') %> 12 |
    13 |

    JSONP Injection

    14 |
    15 |

    Application is using JSONP for fetching wallet balance information your goal is to steal wallet information of other users. 16 |

    17 |
    18 |

    Your Wallet

    19 |

    20 |

    Total Value:


    22 | Bitcoin: 24 |
    <%= BTC %>
    25 | Ethereum: 27 |
    <%= ETH %>
    28 |

    29 |
    30 |
    31 |

    32 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /views/jwt1.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <%- include ('includes/header.ejs') %> 8 | 9 | 10 | 11 | <%- include ('includes/navigation.ejs') %> 12 |
    13 |

    JWT Weak Secret

    14 |
    15 |

    Application is using weak secret to create JSON web token that can lead to authentication bypass. Your goal is to access API Key of username: vulnlabAdmin

    16 |
    17 |
    18 |
    API Token
    19 |

    21 | 22 |
    23 |
    24 |

    25 |

    Vulnerable Code:

    26 |
    27 |
    28 | 
    29 |   # .env 
    30 |   JWT_SECRET=secret
    31 | 
    32 |   # controllers/auth_controller.js
    33 |   function generateAccessToken(username, email) {
    34 |     const payload = {"username": username};
    35 |     return jwt.sign(payload, secret);
    36 |   }
    37 |                 
    38 |
    39 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /views/login.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <%- include ('includes/header.ejs') %> 6 | 7 | 8 | 9 | <%- include ('includes/navigation.ejs') %> 10 |
    11 |
    12 |
    13 | 14 |
    15 |
    16 | 19 |
    20 | 21 | 22 |
    23 |
    24 | 25 | 26 |
    27 | or Register 28 |

    29 |
    30 | 31 |
    32 | 33 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /views/login_totp_verification.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <%- include ('includes/header.ejs') %> 6 | 7 | 8 | 9 | <%- include ('includes/navigation.ejs') %> 10 |
    11 |
    12 |
    13 | 14 |
    15 |
    16 | 20 |
    21 |
    22 |
    Enter code to login
    23 |
    24 | 25 |
    26 | 28 |

    29 |
    30 | 31 |
    32 | 33 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /views/mongodb-notes.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <%- include ('includes/header.ejs') %> 8 | 9 | 10 | 11 | <%- include ('includes/navigation.ejs') %> 12 |
    13 |

    NoSQL Injection

    14 |
    15 |

    Application is using mongodb to handle user notes your goal is to read a note with the title 16 | SuperSecretNote by 17 | performing NoSQL injection. 18 |

    19 |
    20 |
    21 |
    22 |
    Create Notes
    23 |

    24 |

    25 |

    26 | 27 | 28 |

    29 |
    30 |

    31 |

    32 |
    33 |
    34 |
    35 |
    36 |
    37 |
    38 |
    Your Notes
    39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 |
    TitleNote
    51 |
    52 |
    53 |
    54 |
    55 |

    56 |

    Vulnerable Code:

    57 |
    58 |
     59 | 
     60 |   const mongodb_show_notes_post = (req, res) => {
     61 |     MongoClient.connect(dbURL, mongodb_config, (err, client) => {
     62 |       if (err) return res.status('500').send('MongoDB is not installed, Please follow the installation guideline.');
     63 |       const db = client.db('vuln_nodejs_app')
     64 |       db.collection('mongodb-notes').find({ username: req.body.username }).toArray()
     65 |         .then((notes) => {
     66 |             res.send(notes)
     67 |       })
     68 |     })
     69 |   }
     70 |                 
    71 |
    72 | -------------------------------------------------------------------------------- /views/nosql-javascript-injection.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <%- include ('includes/header.ejs') %> 8 | 9 | 10 | 11 | <%- include ('includes/navigation.ejs') %> 12 |
    13 |

    NoSQL Javascript Injection

    14 |
    15 |

    Application has a password protected secret your goal is to access it by using NoSQL injection.

    16 |
    17 |
    18 |
    Secret
    19 |

    21 |

    Password

    23 |

    24 | 25 |
    26 |
    27 |

    28 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /views/notes.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <%- include ('includes/header.ejs') %> 8 | 9 | 10 | 11 | <%- include ('includes/navigation.ejs') %> 12 | 13 |
    14 |

    Notes

    15 |

    Application is loading Notes saved in user account based on the userid supplied in url path your 16 | goal is to access notes saved in vulnlabAdmin account by performing IDOR. 17 |

    18 |
    19 |
    20 |
    21 |
    Create Notes
    22 |

    23 |

    24 |

    25 | 26 | 27 |

    28 |

    29 |
    30 |
    31 |
    32 |
    33 |
    34 |
    35 |
    Your Notes
    36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 |
    idtitle
    48 |
    49 |
    50 |
    51 |
    52 |

    53 |

    Vulnerable Code:

    54 |
    55 |
     56 | 
     57 |   const userNotes_get = (req, res) => {
     58 |   const userid = req.params.userid;
     59 |   Notes.findAll({ where: { userid: userid } })
     60 |     .then((queryResult) => {
     61 |       res.header("Content-Type", "application/json")
     62 |       res.send(JSON.stringify(queryResult));
     63 |     })
     64 |   }
     65 |                 
    66 |
    67 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /views/organization.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <%- include ('includes/header.ejs') %> 8 | 9 | 10 | 11 | <%- include ('includes/navigation.ejs') %> 12 |
    13 |

    Organization Management

    14 |
    15 |

    Your goal is to add yourself in a victim organization by exploiting web messages.

    16 |
    17 |
    18 |
    Create Organization
    19 |

    20 |

    21 | 22 |
    23 |
    24 |
    Org Users
    25 |
    26 |
    27 | 37 |

    38 |
    39 |
    40 |
    41 |

    42 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /views/ping.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <%- include ('includes/header.ejs') %> 8 | 9 | 10 | 11 | <%- include ('includes/navigation.ejs') %> 12 |
    13 |

    Command Injection:

    User supplied input directly passed to exec function without santization.

    14 |
    Vulnerable Code:
    15 |
    16 |
    17 | 
    18 |   const exec = require('child_process').exec
    19 |   ....  
    20 |   const ping = req.body.ping;
    21 |   exec('ping -c 3 ' + req.body.ping, function (err, stdout, stderr){
    22 |     output = stdout + stderr;
    23 |     console.log(output)
    24 |     res.render('ping', {
    25 |       output: output
    26 |     });
    27 |   })
    28 | 
    29 | 
    30 |
    31 |
    32 | 33 | 34 |
    35 | <% if (output) { %> 36 |
    37 |

    Command Output

    38 |
    39 | <%= output %>
    40 | 
    41 | <% } %> 42 | 43 |
    44 |

    Fixed version

    45 |

    In this version application uses `execFile` function from the `child_process` module that starts a specific program 46 | and takes an array of arguments. 47 |

    Fixed Code
    48 |
    49 |
    50 | 
    51 |   const execFile = require('child_process').execFile;
    52 |   ...
    53 |   execFile('/usr/bin/ping', ['-c', '3', ping1], function (err, stdout, stderr){
    54 |     pingoutput = stdout + stderr;
    55 |     res.render('ping', {
    56 |       pingoutput: pingoutput,
    57 |       output: null
    58 |     });
    59 |   });
    60 | 
    61 | 
    62 |
    63 | 64 | 65 |
    66 |
    67 | <% if (pingoutput) { %> 68 |

    Command Output

    69 |
    70 | <%= pingoutput %>
    71 | 
    72 | <% } %> 73 |
    74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /views/register.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <%- include ('includes/header.ejs') %> 6 | 7 | 8 | 9 | <%- include ('includes/navigation.ejs') %> 10 |
    11 |
    12 |
    13 | 14 |
    15 |
    16 | 19 |
    20 | 21 | 22 |
    23 |
    24 | 25 | 26 |
    27 |
    28 | 29 | 30 |
    31 | or Login 33 |

    34 |
    35 | 36 |
    37 | 38 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /views/sqli-fixed.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <%- include ('includes/header.ejs') %> 8 | 9 | 10 | 11 | <%- include ('includes/navigation.ejs') %> 12 |
    13 |

    Check number of trains running from Delhi to your city


    14 |

    Vulnerability is fixed by using using model's `findAll` method instead of executing raw query using pool.

    15 |
    16 |

    To

    17 | 24 | 25 | 26 |
    27 |
    
    28 |     
    29 | 44 |
    45 |

    46 | Fixed Code: 47 |

    48 |
    49 |
    50 | router.get('/sqli-fixed/:from-:to', vuln_handler.fixed_sqli_check_train_get);
    51 | ...
    52 | const fixed_sqli_check_train_get = (req, res) => {
    53 |     const from = req.params.from;
    54 |     const to = req.params.to;
    55 |     db.Train.findAll({where:{from_stnt: from, to_stnt: to }}).then((trains)=> res.send(trains))
    56 |     .catch(()=>res.send('Internal error occured!'));
    57 | }
    58 | 
    59 |
    60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /views/sqli.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <%- include ('includes/header.ejs') %> 8 | 9 | 10 | 11 | <%- include ('includes/navigation.ejs') %> 12 |
    13 |

    SQL Injection

    14 |
    15 |

    Application is concatinating user supplied input to SQL statement without any santization your goal is to extract credentials of vulnlabAdmin account.

    16 |
    17 |
    18 |
    Check trains from Delhi
    19 |

    20 |
    To
    21 | 28 |

    29 | 30 |

    31 |
    
    32 |                 
    33 |
    34 |
    35 |

    36 |

    Vulnerable Code:

    37 |
    38 |
    39 | 
    40 |   router.get('/sqli/:from-:to', vuln_handler.sqli_check_train_get);
    41 |   ...
    42 |   const sqli_check_train_get = (req, res) => {
    43 |     const from = req.params.from;
    44 |     const to = req.params.to;
    45 |     const q = "SELECT ntrains FROM trains where from_stnt='" + from + "' and to_stnt='" + to + "';";
    46 |     con.connect(function (err) {
    47 |         if (err) throw err;
    48 |         con.query(q, (err, results) => {
    49 |             if (err) {
    50 |                 res.send(err);
    51 |             };
    52 |             res.send(JSON.stringify(results));
    53 |         });
    54 |     });
    55 |   }
    56 |                 
    57 |
    58 | 77 |

    Fixed SQL Injection ->

    78 |

    79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /views/svg-xss.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <%- include ('includes/header.ejs') %> 6 | 7 | 8 | 9 | <%- include ('includes/navigation.ejs') %> 10 | 11 |
    12 |

    XSS using SVG file upload

    13 |
    14 |

    Application allow users to upload .svg file as a profile picture your goal is to steal other users 15 | information by using XSS. 16 |

    17 |
    18 |
    19 |
    20 |
    Profile Picture
    21 |

    22 |

    23 |

    26 |

    Only .png,.jpeg,.svg files are allowed

    27 |

    28 |
    29 |
    30 |
    31 |
    32 |
    33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /views/ticket-booking.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <%- include ('includes/header.ejs') %> 8 | 9 | 10 | 11 |
    12 |

    Train Ticket

    13 |

    <%-from_stnt%> ✈ <%-to_stnt%>

    14 |
      15 |
    • Passenger Name: <%-passenger%>
    • 16 |
    • Time: 5:10pm
    • 17 |
    • Date: <%-date%>
    • 18 |
    • Coach: AC 1
    • 19 |
    • Train no: 13306
    • 20 |
    21 |
    22 | 23 | 24 | -------------------------------------------------------------------------------- /views/ticket.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | <%- include ('includes/header.ejs') %> 18 | 19 | 20 | 21 | 22 | 23 | <%- include ('includes/navigation.ejs') %> 24 |
    25 |

    Ticket booking system

    26 |
    27 |

    Book your tickets

    28 |
    29 |
    30 |
    31 |
    32 | 33 | 34 |
    35 |
    36 |
    37 |
    38 |
    39 | 40 | 47 |
    48 |
    49 | 50 | 57 |
    58 |
    59 | 60 | 61 |

    62 | 63 |
    64 |
    65 |
    66 |
    67 |

    68 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /views/totp.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <%- include ('includes/header.ejs') %> 8 | 9 | 10 | 11 | <%- include ('includes/navigation.ejs') %> 12 |
    13 |

    Account Security

    14 |
    15 |

    Your goal is to bypass the 2FA authentication check during login.

    16 |
    17 | 18 |
    19 | <% if (qr != ''){%> 20 |
    Scan barcode using authenticator app
    21 | 22 | 23 | 24 | <%} else {%> 25 |
    Disable 2FA
    26 | 27 | 28 | <% } %> 29 |
    30 |
    31 |
    32 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /views/user-edit.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <%- include ('includes/header.ejs') %> 8 | 9 | 10 | 11 | <%- include ('includes/navigation.ejs') %> 12 |
    13 |

    postMessage XSS

    14 |
    15 |

    Your goal is to perform XSS by exploiting web message.

    16 |
    17 | 18 |
    19 |
    User profile
    20 |
    21 |

    22 |

    23 |

    24 |

    25 |

    34 | 35 |
    36 |
    37 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /views/webmessage-api-token-popup.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /views/webmessage-api-token.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <%- include ('includes/header.ejs') %> 8 | 9 | 10 | 11 | <%- include ('includes/navigation.ejs') %> 12 |
    13 |

    Web Messages Information Disclosure

    14 |
    15 |

    Your goal is to steal the API token by exploiting web messages.

    16 |
    17 |
    18 |
    API Token
    19 |

    21 | 22 |
    23 |
    24 |

    25 |

    Vulnerable Code:

    26 |

    filename: /views/webmessage-api-token-popup.ejs

    27 |
    28 |
    29 | 
    30 |  var token = "<%-apiToken%>";
    31 |  window.opener.postMessage({'token':token}, '*')
    32 |  window.close()
    33 |                 
    34 |
    35 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /views/websocket-xss.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <%- include ('includes/header.ejs') %> 8 | 9 | 10 | 11 | 12 | 13 | <%- include ('includes/navigation.ejs') %> 14 |
    15 |

    WebSocket XSS

    16 |
    17 |

    Application is using socket.io for real-time chat feature, But has only implemented client side input 18 | sanitization your goal is to perform XSS to steal other users session cookie.

    19 |
    20 |
    21 |
    Chat with other users
    22 |

    23 |

    25 |
    26 |

    27 |
    28 |
    29 | 30 |
    31 |
    32 | 34 |
    35 |
    36 |
    37 |
    38 |
    39 |

    Vulnerable Code:

    40 |
    41 |
    42 |   # server.js
    43 | 
    44 |   socket.on('new_message', (data) => {
    45 |     io.sockets.emit('new_message', { message: data.message, username: socket.user.username, login_user: socket.user.username })
    46 |   })
    47 | 
    48 |   # websocket-xss.ejs
    49 |   socket.on('new_message', (data) => {
    50 |     const current_messages = message_box.innerHTML
    51 |     if (data.username === current_user) {
    52 |       message_box.innerHTML = current_messages + `<b><p style="color:rgb(114, 114, 114); display: inline-block;"> You:</p></b> ${data.message}</pre><br>`
    53 |     } else { 
    54 |       message_box.innerHTML = current_messages + `<b><p style="color:rgb(250, 127, 168); display: inline-block;"> ${data.username}:</p></b> ${data.message}</pre><br>`;
    55 |     }
    56 |   }) 
    57 |   
    58 | 
    59 |
    60 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /views/xss.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <%- include ('includes/header.ejs') %> 6 | 7 | 8 | 9 | <%- include ('includes/navigation.ejs') %> 10 |
    11 |
    12 |
    13 |
    Case 1
    14 |

    User supplied input rendered without santizing.

    15 |

    16 |

    17 |
    18 | 19 |
    20 | <% if (xss1){%> 21 |
    22 |
    Welcome <%- xss1 %>
    23 | <% }%> 24 |

    25 |
    26 |
    27 |

    28 |
    29 |
    30 |
    Case 2
    31 |

    HTML encoding is used but user supplied input is rendered inside script tag.

    32 |

    33 |

    34 |
    35 | 36 |
    37 |

    38 |
    39 |
    40 |

    41 |
    42 |
    43 |
    Case 3
    44 |

    XSS inside json object, In this case application is using <%-JSON.stringify("{username": xss3})%> to create a 45 | json object with user supplied input it is using ejs raw output syntax <%- to create a valid json object it does 46 | not use <%= 47 | because it will also encode the double quotes but JSON.stringify will escape the double quotes using backslash 48 | that means we have to find 49 | a way to break out of the double quote to do that we will use this payload 50 | </script><script>alert(1)</script>

    51 |

    52 |

    53 |
    54 | 55 |
    56 |

    57 |
    58 |
    59 | <% if (xss2 || xss3){%> 60 |
    61 | 62 | 66 | <% }%> 67 | 68 | 69 | -------------------------------------------------------------------------------- /views/xxe.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%- include ('includes/header.ejs') %> 5 | 6 | 7 | 8 | <%- include ('includes/navigation.ejs') %> 9 |
    10 |

    XXE Lab

    11 |
    12 |

    Application is using libxmljs for parsing xml which allows parsing of external entities if noent flag is set to true, your goal is to read /etc/passwd file.

    13 |
    14 |
    15 |

    Blog: Lorem Ipsum


    16 |

    Lorem ipsum dolor sit amet consectetur adipisicing elit. Non omnis blanditiis consequuntur. Placeat nobis quaerat esse! Maiores molestias quam at adipisci. Hic sequi qui tempore consectetur aliquid quisquam. Rem, illo. Lorem ipsum dolor sit amet, consectetur adipisicing elit. Error, ab qui dolore et facilis aperiam? Atque quasi repellendus facilis, quibusdam, reiciendis quae unde quia neque quaerat eveniet, ducimus iste mollitia. Lorem ipsum dolor sit amet consectetur adipisicing elit. Nulla optio expedita error dolores et corporis ratione, molestiae minus voluptates rerum, ut non distinctio eaque commodi earum repellendus, temporibus molestias! Ipsa!

    17 |
    18 |

    Comments: 19 |

    
    20 |                 

    21 | 22 |
    23 |
    24 |

    25 |

    Vulnerable Code:

    26 |
    27 |
    28 | 
    29 |   var libxmljs = require('libxmljs');
    30 |   ....
    31 |   const rawcomment = req.body.comment;
    32 |   const parsecomment = libxmljs.parseXmlString(rawcomment, {noent: true,noblanks:true}); // Vuln: noent is set to true
    33 |   var comment = parsecomment.get('//content');
    34 |   comment = comment.text();
    35 |   res.send(comment)
    36 | 
    37 |                 
    38 |
    39 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /vuln-nodejs-app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/payatu/vuln-nodejs-app/bc3c90920ba4e1c668d954e5027d9ca814d2d5c6/vuln-nodejs-app.png -------------------------------------------------------------------------------- /vuln_react_app/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /vuln_react_app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vuln_react_app", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.16.2", 7 | "@testing-library/react": "^12.1.2", 8 | "@testing-library/user-event": "^13.5.0", 9 | "react": "^17.0.2", 10 | "react-dom": "^17.0.2", 11 | "react-router-dom": "^6.2.1", 12 | "react-scripts": "5.0.0", 13 | "web-vitals": "^2.1.4" 14 | }, 15 | "scripts": { 16 | "start": "react-scripts start", 17 | "build": "react-scripts build", 18 | "test": "react-scripts test", 19 | "eject": "react-scripts eject" 20 | }, 21 | "proxy": "http://localhost:9000", 22 | "eslintConfig": { 23 | "extends": [ 24 | "react-app", 25 | "react-app/jest" 26 | ] 27 | }, 28 | "browserslist": { 29 | "production": [ 30 | ">0.2%", 31 | "not dead", 32 | "not op_mini all" 33 | ], 34 | "development": [ 35 | "last 1 chrome version", 36 | "last 1 firefox version", 37 | "last 1 safari version" 38 | ] 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /vuln_react_app/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 15 | Vulnerable NodeJS & ReactJS Application 16 | 17 | 18 | 19 |
    20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /vuln_react_app/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "vuln-nodejs-app", 3 | "name": "Vulnerable NodeJS Application", 4 | "icons": [ 5 | { 6 | "src": "react-favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "react-logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "react-logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /vuln_react_app/public/react-favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/payatu/vuln-nodejs-app/bc3c90920ba4e1c668d954e5027d9ca814d2d5c6/vuln_react_app/public/react-favicon.ico -------------------------------------------------------------------------------- /vuln_react_app/public/react-logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/payatu/vuln-nodejs-app/bc3c90920ba4e1c668d954e5027d9ca814d2d5c6/vuln_react_app/public/react-logo192.png -------------------------------------------------------------------------------- /vuln_react_app/public/react-logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/payatu/vuln-nodejs-app/bc3c90920ba4e1c668d954e5027d9ca814d2d5c6/vuln_react_app/public/react-logo512.png -------------------------------------------------------------------------------- /vuln_react_app/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /vuln_react_app/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | pointer-events: none; 8 | } 9 | 10 | @media (prefers-reduced-motion: no-preference) { 11 | .App-logo { 12 | animation: App-logo-spin infinite 20s linear; 13 | } 14 | } 15 | 16 | .App-header { 17 | background-color: #282c34; 18 | min-height: 100vh; 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | justify-content: center; 23 | font-size: calc(10px + 2vmin); 24 | color: white; 25 | } 26 | 27 | .App-link { 28 | color: #61dafb; 29 | } 30 | 31 | @keyframes App-logo-spin { 32 | from { 33 | transform: rotate(0deg); 34 | } 35 | to { 36 | transform: rotate(360deg); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /vuln_react_app/src/App.js: -------------------------------------------------------------------------------- 1 | import logo from './logo.svg'; 2 | import './App.css'; 3 | import Header from './MyComponents/Header'; 4 | import { BrowserRouter, Route, Routes } from 'react-router-dom' 5 | import React_href_xss from './MyComponents/React_href_xss'; 6 | import React_ref_innerHTML_xss from './MyComponents/React_ref_innerHTML_xss'; 7 | function App() { 8 | return ( 9 | <> 10 |
    11 | 12 | 13 | }/> 14 | }/> 15 | 16 | 17 | 18 | ); 19 | } 20 | 21 | export default App; 22 | -------------------------------------------------------------------------------- /vuln_react_app/src/App.test.js: -------------------------------------------------------------------------------- 1 | import { render, screen } from '@testing-library/react'; 2 | import App from './App'; 3 | 4 | test('renders learn react link', () => { 5 | render(); 6 | const linkElement = screen.getByText(/learn react/i); 7 | expect(linkElement).toBeInTheDocument(); 8 | }); 9 | -------------------------------------------------------------------------------- /vuln_react_app/src/MyComponents/Header.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default function Header() { 4 | return ( 5 | <> 6 | 9 | 10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /vuln_react_app/src/MyComponents/React_href_xss.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | 3 | 4 | class React_href_xss extends Component { 5 | 6 | constructor() { 7 | super(); 8 | this.state = { 9 | name: this.name, 10 | email: this.email, 11 | website: this.website 12 | } 13 | this.saveData = this.saveData.bind(this); 14 | this.updateForm = this.updateForm.bind(this); 15 | 16 | } 17 | updateForm() { 18 | document.getElementById('updated').setAttribute('hidden', true); 19 | document.getElementById('update').removeAttribute('hidden'); 20 | } 21 | 22 | async saveData() { 23 | const name = document.getElementById('name').value; 24 | const email = document.getElementById('email').value; 25 | const website = document.getElementById('website').value; 26 | const request = await fetch(`${window.location.origin}/react-xss`, { 27 | method: 'POST', 28 | headers: { 29 | 'Content-Type': 'application/json', 30 | 'Accept': 'application/json' 31 | }, 32 | body: JSON.stringify({ name: name, email: email, website: website }) 33 | }); 34 | const response = await request.json(); 35 | console.log(response) 36 | this.setState({ name: response.name, email: response.email, website: response.website }) 37 | document.getElementById('update').setAttribute('hidden', true); 38 | document.getElementById('updated').removeAttribute('hidden'); 39 | } 40 | 41 | render() { 42 | return ( 43 |
    44 |
    45 |
    46 |

    React XSS

    47 |
    48 |

    Application is using ReactJS which provides by default protection from XSS attacks by 49 | encoding dangerous characters before appending it to the page but there are 50 | cases when it will not protect you from XSS attacks. In this exercise you will learn how you 51 | can perform XSS when application passes user supplied input to href attribute.

    52 |
    53 |
    54 |
    Update Profile
    55 |
    56 |

    57 | Name:

    58 | Email:

    59 | Website:

    60 | 61 | 62 |

    63 |
    64 | 72 |
    73 |
    74 |
    75 |
    76 | ) 77 | } 78 | } 79 | export default React_href_xss; 80 | -------------------------------------------------------------------------------- /vuln_react_app/src/MyComponents/React_ref_innerHTML_xss.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import ReactDOM from 'react-dom'; 3 | 4 | export class React_ref_innerHTML_xss extends Component { 5 | 6 | constructor() { 7 | super(); 8 | this.nameRef = React.createRef(); 9 | this.emailRef = React.createRef(); 10 | this.websiteRef = React.createRef(); 11 | 12 | this.saveData = this.saveData.bind(this); 13 | this.updateForm = this.updateForm.bind(this); 14 | } 15 | 16 | updateForm() { 17 | document.getElementById('updated').setAttribute('hidden', true); 18 | document.getElementById('update').removeAttribute('hidden'); 19 | } 20 | 21 | async saveData() { 22 | const name = document.getElementById('name').value; 23 | const email = document.getElementById('email').value; 24 | const website = document.getElementById('website').value; 25 | const request = await fetch(`${window.location.origin}/react-xss`, { 26 | method: 'POST', 27 | headers: { 28 | 'Content-Type': 'application/json', 29 | 'Accept': 'application/json' 30 | }, 31 | body: JSON.stringify({ name: name, email: email, website: website }) 32 | }); 33 | const response = await request.json(); 34 | this.nameRef.current.innerHTML = response.name; 35 | this.emailRef.current.innerHTML = response.email; 36 | this.websiteRef.current.setAttribute('href', response.website); 37 | this.websiteRef.current.innerHTML = response.website; 38 | document.getElementById('update').setAttribute('hidden', true); 39 | document.getElementById('updated').removeAttribute('hidden'); 40 | } 41 | 42 | render() { 43 | return ( 44 |
    45 |
    46 |
    47 |
    48 |
    49 |

    React ref-innerHTML XSS


    50 |

    51 | ReactJS provides escape hatch to provide direct access to DOM elements. With direct access 52 | application can perform the desired operation, without requiring explicit support from React. 53 | There are two escape hatches provided by ReactJS which give access to native DOM elements: findDOMNode and createRef. 54 | In this exercise application is using refs with innerHTML property to display user supplied input which makes it vulnerable to XSS. 55 |

    56 |
    57 |
    58 |
    Update Profile
    59 |
    60 |

    61 | Name:

    62 | Email:

    63 | Website:

    64 | 65 | 66 |

    67 |
    68 | 76 |
    77 |
    78 |
    79 |
    80 | ) 81 | } 82 | } 83 | 84 | export default React_ref_innerHTML_xss 85 | -------------------------------------------------------------------------------- /vuln_react_app/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /vuln_react_app/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import reportWebVitals from './reportWebVitals'; 6 | 7 | ReactDOM.render( 8 | 9 | 10 | , 11 | document.getElementById('root') 12 | ); 13 | 14 | // If you want to start measuring performance in your app, pass a function 15 | // to log results (for example: reportWebVitals(console.log)) 16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 17 | reportWebVitals(); 18 | -------------------------------------------------------------------------------- /vuln_react_app/src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vuln_react_app/src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = onPerfEntry => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 4 | getCLS(onPerfEntry); 5 | getFID(onPerfEntry); 6 | getFCP(onPerfEntry); 7 | getLCP(onPerfEntry); 8 | getTTFB(onPerfEntry); 9 | }); 10 | } 11 | }; 12 | 13 | export default reportWebVitals; 14 | -------------------------------------------------------------------------------- /vuln_react_app/src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | --------------------------------------------------------------------------------