Rewrite lesson to be self-contained and not depend on the core of WebGoat for fetching users

Split the assignment into 2 assignments
This commit is contained in:
Nanne Baars 2021-11-04 17:04:23 +01:00 committed by Nanne Baars
parent 9e6ed11aa7
commit 3ad51e6d6b
21 changed files with 409 additions and 331 deletions

View File

@ -34,15 +34,14 @@ public class WebGoatUser implements UserDetails {
}
public WebGoatUser(String username, String password) {
this.username = username;
this.password = password;
createUser();
this(username, password, ROLE_USER);
}
public WebGoatUser(String username, String password, String role) {
this.username = username;
this.password = password;
this.role = role;
createUser();
}

View File

@ -1,54 +1,87 @@
package org.owasp.webgoat;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import org.apache.http.HttpStatus;
import org.junit.jupiter.api.Test;
import java.util.HashMap;
import java.util.Map;
import org.junit.jupiter.api.Test;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import lombok.Data;
public class AccessControlTest extends IntegrationTest {
@Test
@Test
public void testLesson() {
startLesson("MissingFunctionAC");
startLesson("MissingFunctionAC");
assignment1();
assignment2();
assignment3();
Map<String, Object> params = new HashMap<>();
params.clear();
params.put("hiddenMenu1", "Users");
params.put("hiddenMenu2", "Config");
checkResults("/access-control");
}
private void assignment3() {
//direct call should fail if user has not been created
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.contentType(ContentType.JSON)
.get(url("/WebGoat/access-control/users-admin-fix"))
.then()
.statusCode(HttpStatus.SC_FORBIDDEN);
checkAssignment(url("/WebGoat/access-control/hidden-menu"), params, true);
String userHash =
//create user
var userTemplate = """
{"username":"%s","password":"%s","admin": "true"}
""";
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.contentType(ContentType.JSON)
.body(String.format(userTemplate, getWebgoatUser(), getWebgoatUser()))
.post(url("/WebGoat/access-control/users"))
.then()
.statusCode(HttpStatus.SC_OK);
//get the users
var userHash =
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.contentType(ContentType.JSON)
.get(url("/WebGoat/users"))
.get(url("/WebGoat/access-control/users-admin-fix"))
.then()
.statusCode(200)
.extract()
.jsonPath()
.get("find { it.username == \"" + getWebgoatUser() + "\" }.userHash");
.get("find { it.username == \"Jerry\" }.userHash");
params.clear();
params.put("userHash", userHash);
checkAssignment(url("/WebGoat/access-control/user-hash"), params, true);
checkResults("/access-control");
checkAssignment(url("/WebGoat/access-control/user-hash-fix"), Map.of("userHash", userHash), true);
}
@Data
public class Item {
private String username;
private boolean admin;
private String userHash;
private void assignment2() {
var userHash =
RestAssured.given()
.when()
.relaxedHTTPSValidation()
.cookie("JSESSIONID", getWebGoatCookie())
.contentType(ContentType.JSON)
.get(url("/WebGoat/access-control/users"))
.then()
.statusCode(200)
.extract()
.jsonPath()
.get("find { it.username == \"Jerry\" }.userHash");
checkAssignment(url("/WebGoat/access-control/user-hash"), Map.of("userHash", userHash), true);
}
private void assignment1() {
var params = Map.of("hiddenMenu1", "Users", "hiddenMenu2", "Config");
checkAssignment(url("/WebGoat/access-control/hidden-menu"), params, true);
}
}

View File

@ -1,8 +1,8 @@
package org.owasp.webgoat.missing_ac;
import org.owasp.webgoat.users.WebGoatUser;
import org.springframework.security.core.GrantedAuthority;
import lombok.Getter;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Base64;
@ -31,55 +31,32 @@ import java.util.Base64;
* projects.
* <p>
*/
@Getter
public class DisplayUser {
//intended to provide a display version of WebGoatUser for admins to view user attributes
private String username;
private boolean admin;
private String userHash;
public DisplayUser(WebGoatUser user) {
public DisplayUser(User user, String passwordSalt) {
this.username = user.getUsername();
this.admin = false;
this.admin = user.isAdmin();
for (GrantedAuthority authority : user.getAuthorities()) {
this.admin = (authority.getAuthority().contains("WEBGOAT_ADMIN")) ? true : false;
}
// create userHash on the fly
//TODO: persist userHash
try {
this.userHash = genUserHash(user.getUsername(), user.getPassword());
this.userHash = genUserHash(user.getUsername(), user.getPassword(), passwordSalt);
} catch (Exception ex) {
//TODO: implement better fallback operation
this.userHash = "Error generating user hash";
}
}
protected String genUserHash(String username, String password) throws Exception {
protected String genUserHash(String username, String password, String passwordSalt) throws Exception {
MessageDigest md = MessageDigest.getInstance("SHA-256");
// salting is good, but static & too predictable ... short too for a salt
String salted = password + "DeliberatelyInsecure1234" + username;
String salted = password + passwordSalt + username;
//md.update(salted.getBytes("UTF-8")); // Change this to "UTF-16" if needed
byte[] hash = md.digest(salted.getBytes("UTF-8"));
String encoded = Base64.getEncoder().encodeToString(hash);
return encoded;
}
public String getUsername() {
return username;
}
public boolean isAdmin() {
return admin;
}
public String getUserHash() {
return userHash;
byte[] hash = md.digest(salted.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(hash);
}
}

View File

@ -0,0 +1,45 @@
package org.owasp.webgoat.missing_ac;
import org.owasp.webgoat.LessonDataSource;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.List;
@Component
public class MissingAccessControlUserRepository {
private final NamedParameterJdbcTemplate jdbcTemplate;
private final RowMapper<User> mapper = (rs, rowNum) -> new User(rs.getString("username"), rs.getString("password"), rs.getBoolean("admin"));
public MissingAccessControlUserRepository(LessonDataSource lessonDataSource) {
this.jdbcTemplate = new NamedParameterJdbcTemplate(lessonDataSource);
}
public List<User> findAllUsers() {
return jdbcTemplate.query("select username, password, admin from access_control_users", mapper);
}
public User findByUsername(String username) {
var users = jdbcTemplate.query("select username, password, admin from access_control_users where username=:username",
new MapSqlParameterSource().addValue("username", username),
mapper);
if (CollectionUtils.isEmpty(users)) {
return null;
}
return users.get(0);
}
public User save(User user) {
jdbcTemplate.update("INSERT INTO access_control_users(username, password, admin) VALUES(:username,:password,:admin)",
new MapSqlParameterSource()
.addValue("username", user.getUsername())
.addValue("password", user.getPassword())
.addValue("admin", user.isAdmin()));
return user;
}
}

View File

@ -27,7 +27,10 @@ import org.owasp.webgoat.lessons.Lesson;
import org.springframework.stereotype.Component;
@Component
public class MissingFunctionAC extends Lesson {
public class MissingFunctionAC extends Lesson {
public static final String PASSWORD_SALT_SIMPLE = "DeliberatelyInsecure1234";
public static final String PASSWORD_SALT_ADMIN = "DeliberatelyInsecure1235";
@Override
public Category getDefaultCategory() {

View File

@ -25,8 +25,6 @@ package org.owasp.webgoat.missing_ac;
import org.owasp.webgoat.assignments.AssignmentEndpoint;
import org.owasp.webgoat.assignments.AssignmentHints;
import org.owasp.webgoat.assignments.AttackResult;
import org.owasp.webgoat.session.UserSessionData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
@ -37,10 +35,6 @@ import org.springframework.web.bind.annotation.RestController;
@RestController
@AssignmentHints({"access-control.hidden-menus.hint1","access-control.hidden-menus.hint2","access-control.hidden-menus.hint3"})
public class MissingFunctionACHiddenMenus extends AssignmentEndpoint {
//UserSessionData is bound to session and can be used to persist data across multiple assignments
@Autowired
UserSessionData userSessionData;
@PostMapping(path = "/access-control/hidden-menu", produces = {"application/json"})
@ResponseBody

View File

@ -22,79 +22,82 @@
package org.owasp.webgoat.missing_ac;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.owasp.webgoat.users.UserService;
import org.owasp.webgoat.users.WebGoatUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.owasp.webgoat.session.WebSession;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import static org.owasp.webgoat.missing_ac.MissingFunctionAC.PASSWORD_SALT_ADMIN;
import static org.owasp.webgoat.missing_ac.MissingFunctionAC.PASSWORD_SALT_SIMPLE;
/**
* Created by jason on 1/5/17.
*/
@Controller
@AllArgsConstructor
@Slf4j
public class MissingFunctionACUsers {
// this will actually put controllers on the /WebGoat/* path ... the jsp for list_users restricts what can be seen, but the add_user is not controlled carefully
@Autowired
private UserService userService;
private final MissingAccessControlUserRepository userRepository;
private final WebSession webSession;
@RequestMapping(path = {"users"}, method = RequestMethod.GET)
public ModelAndView listUsers(HttpServletRequest request) {
@GetMapping(path = {"access-control/users"})
public ModelAndView listUsers() {
ModelAndView model = new ModelAndView();
model.setViewName("list_users");
List<WebGoatUser> allUsers = userService.getAllUsers();
model.addObject("numUsers",allUsers.size());
List<User> allUsers = userRepository.findAllUsers();
model.addObject("numUsers", allUsers.size());
//add display user objects in place of direct users
List<DisplayUser> displayUsers = new ArrayList<>();
for (WebGoatUser user : allUsers) {
displayUsers.add(new DisplayUser(user));
for (User user : allUsers) {
displayUsers.add(new DisplayUser(user, PASSWORD_SALT_SIMPLE));
}
model.addObject("allUsers",displayUsers);
model.addObject("allUsers", displayUsers);
return model;
}
@RequestMapping(path = {"users", "/"}, method = RequestMethod.GET,consumes = "application/json")
@GetMapping(path = {"access-control/users"}, consumes = "application/json")
@ResponseBody
public List<DisplayUser> usersService(HttpServletRequest request) {
List<WebGoatUser> allUsers = userService.getAllUsers();
List<DisplayUser> displayUsers = new ArrayList<>();
for (WebGoatUser user : allUsers) {
displayUsers.add(new DisplayUser(user));
}
return displayUsers;
public ResponseEntity<List<DisplayUser>> usersService() {
return ResponseEntity.ok(userRepository.findAllUsers().stream().map(user -> new DisplayUser(user, PASSWORD_SALT_SIMPLE)).collect(Collectors.toList()));
}
@RequestMapping(path = {"users","/"}, method = RequestMethod.POST, consumes = "application/json", produces = "application/json")
@GetMapping(path = {"access-control/users-admin-fix"}, consumes = "application/json")
@ResponseBody
//@PreAuthorize()
public WebGoatUser addUser(@RequestBody WebGoatUser newUser) {
public ResponseEntity<List<DisplayUser>> usersFixed() {
var currentUser = userRepository.findByUsername(webSession.getUserName());
if (currentUser != null && currentUser.isAdmin()) {
return ResponseEntity.ok(userRepository.findAllUsers().stream().map(user -> new DisplayUser(user, PASSWORD_SALT_ADMIN)).collect(Collectors.toList()));
}
return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
}
@PostMapping(path = {"access-control/users", "access-control/users-admin-fix"}, consumes = "application/json", produces = "application/json")
@ResponseBody
public User addUser(@RequestBody User newUser) {
try {
userService.addUser(newUser.getUsername(),newUser.getPassword());
return userService.loadUserByUsername(newUser.getUsername());
userRepository.save(newUser);
return newUser;
} catch (Exception ex) {
log.error("Error creating new User", ex);
//TODO: implement error handling ...
} finally {
// no streams or other resources opened ... nothing to do, right?
return null;
}
return null;
//@RequestMapping(path = {"user/{username}","/"}, method = RequestMethod.DELETE, consumes = "application/json", produces = "application/json")
//TODO implement delete method with id param and authorization
}
//@RequestMapping(path = {"user/{username}","/"}, method = RequestMethod.DELETE, consumes = "application/json", produces = "application/json")
//TODO implement delete method with id param and authorization
}

View File

@ -22,35 +22,33 @@
package org.owasp.webgoat.missing_ac;
import lombok.RequiredArgsConstructor;
import org.owasp.webgoat.assignments.AssignmentEndpoint;
import org.owasp.webgoat.assignments.AssignmentHints;
import org.owasp.webgoat.assignments.AttackResult;
import org.owasp.webgoat.users.UserService;
import org.owasp.webgoat.users.WebGoatUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import static org.owasp.webgoat.missing_ac.MissingFunctionAC.PASSWORD_SALT_SIMPLE;
@RestController
@AssignmentHints({"access-control.hash.hint1","access-control.hash.hint2","access-control.hash.hint3",
"access-control.hash.hint4","access-control.hash.hint5","access-control.hash.hint6","access-control.hash.hint7",
"access-control.hash.hint8","access-control.hash.hint9","access-control.hash.hint10","access-control.hash.hint11","access-control.hash.hint12"})
@AssignmentHints({"access-control.hash.hint1", "access-control.hash.hint2", "access-control.hash.hint3", "access-control.hash.hint4", "access-control.hash.hint5"})
@RequiredArgsConstructor
public class MissingFunctionACYourHash extends AssignmentEndpoint {
@Autowired
private UserService userService;
private final MissingAccessControlUserRepository userRepository;
@PostMapping(path = "/access-control/user-hash", produces = {"application/json"})
@ResponseBody
public AttackResult completed(String userHash) {
String currentUser = getWebSession().getUserName();
WebGoatUser user = userService.loadUserByUsername(currentUser);
DisplayUser displayUser = new DisplayUser(user);
public AttackResult simple(String userHash) {
User user = userRepository.findByUsername("Jerry");
DisplayUser displayUser = new DisplayUser(user, PASSWORD_SALT_SIMPLE);
if (userHash.equals(displayUser.getUserHash())) {
return success(this).feedback("access-control.hash.success").build();
} else {
return failed(this).feedback("access-control.hash.close").build();
return failed(this).build();
}
}
}

View File

@ -0,0 +1,60 @@
/*
* This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/
*
* Copyright (c) 2002 - 2019 Bruce Mayhew
*
* This program 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 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program; if
* not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*
* Getting Source ==============
*
* Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects.
*/
package org.owasp.webgoat.missing_ac;
import org.owasp.webgoat.assignments.AssignmentEndpoint;
import org.owasp.webgoat.assignments.AssignmentHints;
import org.owasp.webgoat.assignments.AttackResult;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import static org.owasp.webgoat.missing_ac.MissingFunctionAC.PASSWORD_SALT_ADMIN;
@RestController
@AssignmentHints({"access-control.hash.hint6", "access-control.hash.hint7",
"access-control.hash.hint8", "access-control.hash.hint9", "access-control.hash.hint10", "access-control.hash.hint11", "access-control.hash.hint12"})
public class MissingFunctionACYourHashAdmin extends AssignmentEndpoint {
private final MissingAccessControlUserRepository userRepository;
public MissingFunctionACYourHashAdmin(MissingAccessControlUserRepository userRepository) {
this.userRepository = userRepository;
}
@PostMapping(path = "/access-control/user-hash-fix", produces = {"application/json"})
@ResponseBody
public AttackResult admin(String userHash) {
//current user should be in the DB
//if not admin then return 403
var user = userRepository.findByUsername("Jerry");
var displayUser = new DisplayUser(user, PASSWORD_SALT_ADMIN);
if (userHash.equals(displayUser.getUserHash())) {
return success(this).feedback("access-control.hash.success").build();
} else {
return failed(this).feedback("access-control.hash.close").build();
}
}
}

View File

@ -0,0 +1,15 @@
package org.owasp.webgoat.missing_ac;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private String username;
private String password;
private boolean admin;
}

View File

@ -1,117 +0,0 @@
/*
* This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/
*
* Copyright (c) 2002 - 2019 Bruce Mayhew
*
* This program 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 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program; if
* not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*
* Getting Source ==============
*
* Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects.
*/
package org.owasp.webgoat.missing_ac;
import org.owasp.webgoat.LessonDataSource;
import org.owasp.webgoat.session.UserSessionData;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
public class Users {
private UserSessionData userSessionData;
private LessonDataSource dataSource;
public Users(UserSessionData userSessionData, LessonDataSource dataSource) {
this.userSessionData = userSessionData;
this.dataSource = dataSource;
}
@GetMapping(produces = {"application/json"})
@ResponseBody
protected HashMap<Integer, HashMap> getUsers() {
try (Connection connection = dataSource.getConnection()) {
String query = "SELECT * FROM user_data";
try {
Statement statement = connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_READ_ONLY);
ResultSet results = statement.executeQuery(query);
HashMap<Integer, HashMap> allUsersMap = new HashMap();
if ((results != null) && (results.first() == true)) {
while (results.next()) {
HashMap<String, String> userMap = new HashMap<>();
userMap.put("first", results.getString(1));
userMap.put("last", results.getString(2));
userMap.put("cc", results.getString(3));
userMap.put("ccType", results.getString(4));
userMap.put("cookie", results.getString(5));
userMap.put("loginCount", Integer.toString(results.getInt(6)));
allUsersMap.put(results.getInt(0), userMap);
}
userSessionData.setValue("allUsers", allUsersMap);
return allUsersMap;
}
} catch (SQLException sqle) {
sqle.printStackTrace();
HashMap<String, String> errMap = new HashMap() {{
put("err", sqle.getErrorCode() + "::" + sqle.getMessage());
}};
return new HashMap<Integer, HashMap>() {{
put(0, errMap);
}};
} catch (Exception e) {
e.printStackTrace();
HashMap<String, String> errMap = new HashMap() {{
put("err", e.getMessage() + "::" + e.getCause());
}};
e.printStackTrace();
return new HashMap<Integer, HashMap>() {{
put(0, errMap);
}};
} finally {
try {
if (connection != null) {
connection.close();
}
} catch (SQLException sqle) {
sqle.printStackTrace();
}
}
} catch (Exception e) {
e.printStackTrace();
HashMap<String, String> errMap = new HashMap() {{
put("err", e.getMessage() + "::" + e.getCause());
}};
e.printStackTrace();
return new HashMap<>() {{
put(0, errMap);
}};
}
return null;
}
}

View File

@ -0,0 +1,4 @@
.hidden-menu-item {
display:none;
visibility:hidden;
}

View File

@ -0,0 +1,9 @@
CREATE TABLE access_control_users(
username varchar(40),
password varchar(40),
admin boolean
);
INSERT INTO access_control_users VALUES ('Tom', 'qwertyqwerty1234', false);
INSERT INTO access_control_users VALUES ('Jerry', 'doesnotreallymatter', true);
INSERT INTO access_control_users VALUES ('Sylvester', 'testtesttest', false);

View File

@ -5,6 +5,7 @@
</div>
<div class="lesson-page-wrapper">
<link rel="stylesheet" type="text/css" th:href="@{/lesson_css/ac.css}"/>
<div class="adoc-content" th:replace="doc:missing-function-ac-02-client-controls.adoc"></div>
<div class="attack-container">
@ -17,7 +18,6 @@
<div class="collapse navbar-collapse" id="alignment-example">
<!-- Links -->
<ul class="nav navbar-nav">
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Account<span class="caret"></span></a>
@ -37,8 +37,9 @@
<li class="hidden-menu-item dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Admin<span class="caret"></span></a>
<ul class="dropdown-menu" aria-labelledby="admin">
<li><a href="/users">Users</a></li>
<li><a href="/config">Config</a></li>
<li><a href="/access-control/users">Users</a></li>
<li><a href="/access-control/users-admin-fix">Users</a></li>
<li><a href="/access-control/config">Config</a></li>
</ul>
</li>
</ul>
@ -89,4 +90,26 @@
</div>
<div class="lesson-page-wrapper">
<div class="adoc-content" th:replace="doc:missing-function-ac-04-users-fixed.adoc"></div>
<div class="attack-container">
<div class="assignment-success"><i class="fa fa-2 fa-check hidden" aria-hidden="true"></i></div>
<form class="attack-form" accept-charset="UNKNOWN"
method="POST" name="form"
action="/WebGoat/access-control/user-hash-fix">
<p>Your Hash: <input name="userHash" value="" type="TEXT"/></p>
<br/>
<input name="submit" value="Submit" type="SUBMIT"/>
</form>
<div class="attack-feedback"></div>
<div class="attack-output"></div>
</div>
</div>
</html>

View File

@ -11,15 +11,15 @@ access-control.hidden-menus.hint3=Look for something a super-user or administato
access-control.hash.success=Congrats! You really succeeded when you added the user.
access-control.hash.close=Keep trying, this one may take several attempts & steps to achieve. See the hints for help.
access-control.hash.hint1=There is an easier way and a 'harder' way to achieve this, the easier way involves one simple change in a GET request.
access-control.hash.hint2= If you haven't found the hidden menus from the earlier exercise, go do that first.
access-control.hash.hint1=This assignment involves one simple change in a GET request.
access-control.hash.hint2=If you haven't found the hidden menus from the earlier exercise, go do that first.
access-control.hash.hint3=When you look at the users page, there is a hint that more info is viewable by a given role.
access-control.hash.hint4=For the easy way, have you tried tampering the GET request? Different content-types?
access-control.hash.hint5=For the 'easy' way, modify the GET request to /users to include 'Content-Type: application/json'
access-control.hash.hint4=Have you tried tampering the GET request? Different content-types?
access-control.hash.hint5=Modify the GET request to `/access-control/users` to include 'Content-Type: application/json'
access-control.hash.hint6=Now for the harder way ... it builds on the easier way
access-control.hash.hint7=If the request to view users, were a 'service' or 'RESTful' endpoint, what would be different about it?
access-control.hash.hint8=If you're still looking for hints ... try changing the Content-type header as in the GET request.
access-control.hash.hint9=You also need to deliver a proper payload for the request (look at how registration works). This should be formatted in line with the content-type you just defined.
access-control.hash.hint10=You will want to add WEBGOAT_ADMIN for the user's role. Yes, you'd have to guess/fuzz this in a real-world setting.
access-control.hash.hint11=OK, here it is. First, create an admin user ... Change the method to POST, change the content-type to "application/json". And your payload should look something like: {"username":"newUser2","password":"newUser12","matchingPassword":"newUser12","role":"WEBGOAT_ADMIN"}
access-control.hash.hint10=You will want to add your own username with an admin role. Yes, you'd have to guess/fuzz this in a real-world setting.
access-control.hash.hint11=OK, here it is. First, create an admin user ... Change the method to POST, change the content-type to "application/json". And your payload should look something like: {"username":"newUser2","password":"newUser12","admin": "true"}
access-control.hash.hint12=Now log in as that user and bring up WebGoat/users. Copy your hash and log back in to your original account and input it there to get credit.

View File

@ -12,4 +12,4 @@ It will likely take multiple steps and multiple attempts to get this one:
- You'll need to do some guessing too.
- You may need to use another browser/account along the way.
Start with the information you already gathered (hidden menu items) to see if you can pull the list of users and then provide the 'Hash' for your user account.
Start with the information you already gathered (hidden menu items) to see if you can pull the list of users and then provide the 'hash' for Jerry's account.

View File

@ -0,0 +1,5 @@
== The company fixed the problem, right?
The company found out the endpoint was a bit too open, they made an emergency fixed and not only admin users can list all users.
Start with the information you already gathered (hidden menu items) to see if you can pull the list of users and then provide the 'hash' for Jerry's account.

View File

@ -22,23 +22,22 @@
package org.owasp.webgoat.missing_ac;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
import org.owasp.webgoat.users.WebGoatUser;
@ExtendWith(MockitoExtension.class)
public class DisplayUserTest {
import static org.owasp.webgoat.missing_ac.MissingFunctionAC.PASSWORD_SALT_SIMPLE;
class DisplayUserTest {
@Test
public void TestDisplayUserCreation() {
DisplayUser displayUser = new DisplayUser(new WebGoatUser("user1","password1"));
assert(!displayUser.isAdmin());
void testDisplayUserCreation() {
DisplayUser displayUser = new DisplayUser(new User("user1", "password1", true), PASSWORD_SALT_SIMPLE);
Assertions.assertThat(displayUser.isAdmin()).isTrue();
}
@Test
public void TesDisplayUserHash() {
DisplayUser displayUser = new DisplayUser(new WebGoatUser("user1","password1"));
assert(displayUser.getUserHash().equals("cplTjehjI/e5ajqTxWaXhU5NW9UotJfXj+gcbPvfWWc="));
void testDisplayUserHash() {
DisplayUser displayUser = new DisplayUser(new User("user1", "password1", false), PASSWORD_SALT_SIMPLE);
Assertions.assertThat(displayUser.getUserHash()).isEqualTo("cplTjehjI/e5ajqTxWaXhU5NW9UotJfXj+gcbPvfWWc=");
}
}

View File

@ -28,53 +28,53 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.owasp.webgoat.users.UserService;
import org.owasp.webgoat.users.WebGoatUser;
import org.springframework.test.util.ReflectionTestUtils;
import org.owasp.webgoat.plugins.LessonTest;
import org.owasp.webgoat.session.WebSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import java.util.ArrayList;
import java.util.List;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standaloneSetup;
@ExtendWith(MockitoExtension.class)
public class MissingFunctionACUsersTest {
private MockMvc mockMvc;
@Mock
private UserService userService;
class MissingFunctionACUsersTest extends LessonTest {
@Autowired
private MissingFunctionAC ac;
@BeforeEach
public void setup() {
MissingFunctionACUsers usersController = new MissingFunctionACUsers();
this.mockMvc = standaloneSetup(usersController).build();
ReflectionTestUtils.setField(usersController,"userService",userService);
when(userService.getAllUsers()).thenReturn(getUsersList());
void setup() {
when(webSession.getCurrentLesson()).thenReturn(ac);
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
@Test
public void TestContentTypeApplicationJSON () throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/users")
.header("Content-type","application/json"))
void getUsers() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/access-control/users")
.header("Content-type", "application/json"))
.andExpect(status().isOk())
.andExpect(jsonPath("$[0].username", CoreMatchers.is("user1")))
.andExpect(jsonPath("$[0].userHash",CoreMatchers.is("cplTjehjI/e5ajqTxWaXhU5NW9UotJfXj+gcbPvfWWc=")))
.andExpect(jsonPath("$[1].admin",CoreMatchers.is(true)));
.andExpect(jsonPath("$[0].username", CoreMatchers.is("Tom")))
.andExpect(jsonPath("$[0].userHash", CoreMatchers.is("Mydnhcy00j2b0m6SjmPz6PUxF9WIeO7tzm665GiZWCo=")))
.andExpect(jsonPath("$[0].admin", CoreMatchers.is(false)));
}
private List<WebGoatUser> getUsersList() {
List <WebGoatUser> tempUsers = new ArrayList<>();
tempUsers.add(new WebGoatUser("user1","password1"));
tempUsers.add(new WebGoatUser("user2","password2","WEBGOAT_ADMIN"));
tempUsers.add(new WebGoatUser("user3","password3", "WEBGOAT_USER"));
return tempUsers;
@Test
void addUser() throws Exception {
var user = """
{"username":"newUser","password":"newUser12","admin": "true"}
""";
mockMvc.perform(MockMvcRequestBuilders.post("/access-control/users")
.header("Content-type", "application/json").content(user))
.andExpect(status().isOk());
mockMvc.perform(MockMvcRequestBuilders.get("/access-control/users")
.header("Content-type", "application/json"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.size()", is(4)));
}
}

View File

@ -0,0 +1,44 @@
package org.owasp.webgoat.missing_ac;
import org.hamcrest.CoreMatchers;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.owasp.webgoat.plugins.LessonTest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import static org.mockito.Mockito.when;
import static org.owasp.webgoat.missing_ac.MissingFunctionAC.PASSWORD_SALT_ADMIN;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
class MissingFunctionACYourHashAdminTest extends LessonTest {
@Autowired
private MissingFunctionAC ac;
@BeforeEach
public void setup() {
when(webSession.getCurrentLesson()).thenReturn(ac);
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
@Test
void solve() throws Exception {
var userHash = new DisplayUser(new User("Jerry", "doesnotreallymatter", true), PASSWORD_SALT_ADMIN).getUserHash();
mockMvc.perform(MockMvcRequestBuilders.post("/access-control/user-hash-fix")
.param("userHash", userHash))
.andExpect(status().isOk())
.andExpect(jsonPath("$.feedback", CoreMatchers.containsString("Congrats! You really succeeded when you added the user.")))
.andExpect(jsonPath("$.lessonCompleted", CoreMatchers.is(true)));
}
@Test
void wrongUserHash() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.post("/access-control/user-hash-fix")
.param("userHash", "wrong"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.lessonCompleted", CoreMatchers.is(false)));
}
}

View File

@ -25,56 +25,40 @@ package org.owasp.webgoat.missing_ac;
import org.hamcrest.CoreMatchers;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.mockito.junit.jupiter.MockitoExtension;
import org.owasp.webgoat.assignments.AssignmentEndpointTest;
import org.owasp.webgoat.users.UserService;
import org.owasp.webgoat.plugins.LessonTest;
import org.owasp.webgoat.users.WebGoatUser;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import static org.owasp.webgoat.missing_ac.MissingFunctionAC.PASSWORD_SALT_SIMPLE;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standaloneSetup;
@ExtendWith(MockitoExtension.class)
public class MissingFunctionYourHashTest extends AssignmentEndpointTest {
private MockMvc mockMvc;
private DisplayUser mockDisplayUser;
@Mock
protected UserService userService;
class MissingFunctionYourHashTest extends LessonTest {
@Autowired
private MissingFunctionAC ac;
@BeforeEach
public void setUp() {
MissingFunctionACYourHash yourHashTest = new MissingFunctionACYourHash();
init(yourHashTest);
this.mockMvc = standaloneSetup(yourHashTest).build();
this.mockDisplayUser = new DisplayUser(new WebGoatUser("user", "userPass"));
ReflectionTestUtils.setField(yourHashTest, "userService", userService);
when(userService.loadUserByUsername(any())).thenReturn(new WebGoatUser("user", "userPass"));
public void setup() {
when(webSession.getCurrentLesson()).thenReturn(ac);
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
@Test
public void HashDoesNotMatch() throws Exception {
void hashDoesNotMatch() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.post("/access-control/user-hash")
.param("userHash", "42"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.feedback", CoreMatchers.containsString("Keep trying, this one may take several attempts")))
.andExpect(jsonPath("$.lessonCompleted", CoreMatchers.is(false)));
}
@Test
public void hashMatches() throws Exception {
void hashMatches() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.post("/access-control/user-hash")
.param("userHash", "2340928sadfajsdalsNfwrBla="))
.param("userHash", "SVtOlaa+ER+w2eoIIVE5/77umvhcsh5V8UyDLUa1Itg="))
.andExpect(status().isOk())
.andExpect(jsonPath("$.feedback", CoreMatchers.containsString("Keep trying, this one may take several attempts")))
.andExpect(jsonPath("$.lessonCompleted", CoreMatchers.is(false)));
.andExpect(jsonPath("$.lessonCompleted", CoreMatchers.is(true)));
}
}