From 4785e1148780801b72918d10066ffbf360ed25c1 Mon Sep 17 00:00:00 2001 From: James Spoor Date: Wed, 17 Mar 2021 13:01:06 +0100 Subject: [PATCH 1/3] chore: add sanity tests to sarif converter --- .../shallow_sast_webgoat/Assignment5.java | 70 + .../shallow_sast_webgoat/DeserializeTest.java | 93 ++ .../HashingAssignment.java | 111 ++ .../shallow_sast_webgoat/IDORLogin.java | 75 + .../JWTFinalEndpointTest.java | 98 ++ .../JWTRefreshEndpoint.java | 133 ++ .../JWTVotesEndpoint.java | 177 +++ .../shallow_sast_webgoat/LICENSE.txt | 19 + .../shallow_sast_webgoat/SSRFTask2.java | 82 + .../SqlInjectionLesson4.java | 77 + .../SqlInjectionLesson6a.java | 99 ++ .../SqlInjectionLesson9.java | 110 ++ .../SqlOnlyInputValidation.java | 55 + .../StoredXssComments.java | 105 ++ .../UserValidatorTest.java | 61 + .../shallow_sast_webgoat/Users.java | 117 ++ .../WebSecurityConfig.java | 100 ++ .../shallow_sast_webgoat/jquery.form.js | 1367 +++++++++++++++++ .../shallow_sast_webgoat.iml | 11 + tests/sarif_converter.spec.ts | 91 +- 20 files changed, 3031 insertions(+), 20 deletions(-) create mode 100644 tests/fixtures/sarif_convertor/shallow_sast_webgoat/Assignment5.java create mode 100644 tests/fixtures/sarif_convertor/shallow_sast_webgoat/DeserializeTest.java create mode 100644 tests/fixtures/sarif_convertor/shallow_sast_webgoat/HashingAssignment.java create mode 100644 tests/fixtures/sarif_convertor/shallow_sast_webgoat/IDORLogin.java create mode 100644 tests/fixtures/sarif_convertor/shallow_sast_webgoat/JWTFinalEndpointTest.java create mode 100644 tests/fixtures/sarif_convertor/shallow_sast_webgoat/JWTRefreshEndpoint.java create mode 100644 tests/fixtures/sarif_convertor/shallow_sast_webgoat/JWTVotesEndpoint.java create mode 100644 tests/fixtures/sarif_convertor/shallow_sast_webgoat/LICENSE.txt create mode 100644 tests/fixtures/sarif_convertor/shallow_sast_webgoat/SSRFTask2.java create mode 100644 tests/fixtures/sarif_convertor/shallow_sast_webgoat/SqlInjectionLesson4.java create mode 100644 tests/fixtures/sarif_convertor/shallow_sast_webgoat/SqlInjectionLesson6a.java create mode 100644 tests/fixtures/sarif_convertor/shallow_sast_webgoat/SqlInjectionLesson9.java create mode 100644 tests/fixtures/sarif_convertor/shallow_sast_webgoat/SqlOnlyInputValidation.java create mode 100644 tests/fixtures/sarif_convertor/shallow_sast_webgoat/StoredXssComments.java create mode 100644 tests/fixtures/sarif_convertor/shallow_sast_webgoat/UserValidatorTest.java create mode 100644 tests/fixtures/sarif_convertor/shallow_sast_webgoat/Users.java create mode 100644 tests/fixtures/sarif_convertor/shallow_sast_webgoat/WebSecurityConfig.java create mode 100644 tests/fixtures/sarif_convertor/shallow_sast_webgoat/jquery.form.js create mode 100644 tests/fixtures/sarif_convertor/shallow_sast_webgoat/shallow_sast_webgoat.iml diff --git a/tests/fixtures/sarif_convertor/shallow_sast_webgoat/Assignment5.java b/tests/fixtures/sarif_convertor/shallow_sast_webgoat/Assignment5.java new file mode 100644 index 00000000..e583d72a --- /dev/null +++ b/tests/fixtures/sarif_convertor/shallow_sast_webgoat/Assignment5.java @@ -0,0 +1,70 @@ +/* + * 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.challenges.challenge5; + +import lombok.extern.slf4j.Slf4j; +import org.owasp.webgoat.assignments.AssignmentEndpoint; +import org.owasp.webgoat.assignments.AttackResult; +import org.owasp.webgoat.challenges.Flag; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +import javax.sql.DataSource; +import java.sql.PreparedStatement; +import java.sql.ResultSet; + +@RestController +@Slf4j +public class Assignment5 extends AssignmentEndpoint { + + private final DataSource dataSource; + + public Assignment5(DataSource dataSource) { + this.dataSource = dataSource; + } + + @PostMapping("/challenge/5") + @ResponseBody + public AttackResult login(@RequestParam String username_login, @RequestParam String password_login) throws Exception { + if (!StringUtils.hasText(username_login) || !StringUtils.hasText(password_login)) { + return failed(this).feedback("required4").build(); + } + if (!"Larry".equals(username_login)) { + return failed(this).feedback("user.not.larry").feedbackArgs(username_login).build(); + } + try (var connection = dataSource.getConnection()) { + PreparedStatement statement = connection.prepareStatement("select password from challenge_users where userid = '" + username_login + "' and password = '" + password_login + "'"); + ResultSet resultSet = statement.executeQuery(); + + if (resultSet.next()) { + return success(this).feedback("challenge.solved").feedbackArgs(Flag.FLAGS.get(5)).build(); + } else { + return failed(this).feedback("challenge.close").build(); + } + } + } +} + diff --git a/tests/fixtures/sarif_convertor/shallow_sast_webgoat/DeserializeTest.java b/tests/fixtures/sarif_convertor/shallow_sast_webgoat/DeserializeTest.java new file mode 100644 index 00000000..765a9e8c --- /dev/null +++ b/tests/fixtures/sarif_convertor/shallow_sast_webgoat/DeserializeTest.java @@ -0,0 +1,93 @@ +package org.owasp.webgoat.deserialization; + +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; + +import org.dummy.insecure.framework.VulnerableTaskHolder; +import org.hamcrest.CoreMatchers; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; +import org.owasp.webgoat.assignments.AssignmentEndpointTest; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; + +@RunWith(MockitoJUnitRunner.class) +public class DeserializeTest extends AssignmentEndpointTest { + + private MockMvc mockMvc; + + private static String OS = System.getProperty("os.name").toLowerCase(); + + @Before + public void setup() { + InsecureDeserializationTask insecureTask = new InsecureDeserializationTask(); + init(insecureTask); + this.mockMvc = standaloneSetup(insecureTask).build(); + } + + @Test + public void success() throws Exception { + if (OS.indexOf("win")>-1) { + mockMvc.perform(MockMvcRequestBuilders.post("/InsecureDeserialization/task") + .header("x-request-intercepted", "true") + .param("token", SerializationHelper.toString(new VulnerableTaskHolder("wait", "ping localhost -n 5")))) + .andExpect(status().isOk()).andExpect(jsonPath("$.lessonCompleted", is(true))); + } else { + mockMvc.perform(MockMvcRequestBuilders.post("/InsecureDeserialization/task") + .header("x-request-intercepted", "true") + .param("token", SerializationHelper.toString(new VulnerableTaskHolder("wait", "sleep 5")))) + .andExpect(status().isOk()).andExpect(jsonPath("$.lessonCompleted", is(true))); + } + } + + @Test + public void fail() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.post("/InsecureDeserialization/task") + .header("x-request-intercepted", "true") + .param("token", SerializationHelper.toString(new VulnerableTaskHolder("delete", "rm *")))) + .andExpect(status().isOk()).andExpect(jsonPath("$.lessonCompleted", is(false))); + } + + @Test + public void wrongVersion() throws Exception { + String token = "rO0ABXNyADFvcmcuZHVtbXkuaW5zZWN1cmUuZnJhbWV3b3JrLlZ1bG5lcmFibGVUYXNrSG9sZGVyAAAAAAAAAAECAANMABZyZXF1ZXN0ZWRFeGVjdXRpb25UaW1ldAAZTGphdmEvdGltZS9Mb2NhbERhdGVUaW1lO0wACnRhc2tBY3Rpb250ABJMamF2YS9sYW5nL1N0cmluZztMAAh0YXNrTmFtZXEAfgACeHBzcgANamF2YS50aW1lLlNlcpVdhLobIkiyDAAAeHB3DgUAAAfjCR4GIQgMLRSoeHQACmVjaG8gaGVsbG90AAhzYXlIZWxsbw"; + mockMvc.perform(MockMvcRequestBuilders.post("/InsecureDeserialization/task") + .header("x-request-intercepted", "true") + .param("token", token)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.feedback", CoreMatchers.is(messages.getMessage("insecure-deserialization.invalidversion")))) + .andExpect(jsonPath("$.lessonCompleted", is(false))); + } + + @Test + public void expiredTask() throws Exception { + String token = "rO0ABXNyADFvcmcuZHVtbXkuaW5zZWN1cmUuZnJhbWV3b3JrLlZ1bG5lcmFibGVUYXNrSG9sZGVyAAAAAAAAAAICAANMABZyZXF1ZXN0ZWRFeGVjdXRpb25UaW1ldAAZTGphdmEvdGltZS9Mb2NhbERhdGVUaW1lO0wACnRhc2tBY3Rpb250ABJMamF2YS9sYW5nL1N0cmluZztMAAh0YXNrTmFtZXEAfgACeHBzcgANamF2YS50aW1lLlNlcpVdhLobIkiyDAAAeHB3DgUAAAfjCR4IDC0YfvNIeHQACmVjaG8gaGVsbG90AAhzYXlIZWxsbw"; + mockMvc.perform(MockMvcRequestBuilders.post("/InsecureDeserialization/task") + .header("x-request-intercepted", "true") + .param("token", token)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.feedback", CoreMatchers.is(messages.getMessage("insecure-deserialization.expired")))) + .andExpect(jsonPath("$.lessonCompleted", is(false))); + } + + + + @Test + public void checkOtherObject() throws Exception { + String token = "rO0ABXQAVklmIHlvdSBkZXNlcmlhbGl6ZSBtZSBkb3duLCBJIHNoYWxsIGJlY29tZSBtb3JlIHBvd2VyZnVsIHRoYW4geW91IGNhbiBwb3NzaWJseSBpbWFnaW5l"; + mockMvc.perform(MockMvcRequestBuilders.post("/InsecureDeserialization/task") + .header("x-request-intercepted", "true") + .param("token", token)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.feedback", CoreMatchers.is(messages.getMessage("insecure-deserialization.stringobject")))) + .andExpect(jsonPath("$.lessonCompleted", is(false))); + } + + + +} \ No newline at end of file diff --git a/tests/fixtures/sarif_convertor/shallow_sast_webgoat/HashingAssignment.java b/tests/fixtures/sarif_convertor/shallow_sast_webgoat/HashingAssignment.java new file mode 100644 index 00000000..b5dc69fd --- /dev/null +++ b/tests/fixtures/sarif_convertor/shallow_sast_webgoat/HashingAssignment.java @@ -0,0 +1,111 @@ +/* + * 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.crypto; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Random; + +import javax.servlet.http.HttpServletRequest; +import javax.xml.bind.DatatypeConverter; + +import org.owasp.webgoat.assignments.AssignmentEndpoint; +import org.owasp.webgoat.assignments.AssignmentHints; +import org.owasp.webgoat.assignments.AttackResult; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@AssignmentHints({"crypto-hashing.hints.1","crypto-hashing.hints.2"}) +public class HashingAssignment extends AssignmentEndpoint { + + public static final String[] SECRETS = {"secret","admin","password", "123456", "passw0rd"}; + + @RequestMapping(path="/crypto/hashing/md5",produces=MediaType.TEXT_HTML_VALUE) + @ResponseBody + public String getMd5(HttpServletRequest request) throws NoSuchAlgorithmException { + + String md5Hash = (String) request.getSession().getAttribute("md5Hash"); + if (md5Hash == null) { + + String secret = SECRETS[new Random().nextInt(SECRETS.length)]; + + MessageDigest md = MessageDigest.getInstance("MD5"); + md.update(secret.getBytes()); + byte[] digest = md.digest(); + md5Hash = DatatypeConverter + .printHexBinary(digest).toUpperCase(); + request.getSession().setAttribute("md5Hash", md5Hash); + request.getSession().setAttribute("md5Secret", secret); + } + return md5Hash; + } + + @RequestMapping(path="/crypto/hashing/sha256",produces=MediaType.TEXT_HTML_VALUE) + @ResponseBody + public String getSha256(HttpServletRequest request) throws NoSuchAlgorithmException { + + String sha256 = (String) request.getSession().getAttribute("sha256"); + if (sha256 == null) { + String secret = SECRETS[new Random().nextInt(SECRETS.length)]; + sha256 = getHash(secret, "SHA-256"); + request.getSession().setAttribute("sha256Hash", sha256); + request.getSession().setAttribute("sha256Secret", secret); + } + return sha256; + } + + @PostMapping("/crypto/hashing") + @ResponseBody + public AttackResult completed(HttpServletRequest request, @RequestParam String answer_pwd1, @RequestParam String answer_pwd2) { + + String md5Secret = (String) request.getSession().getAttribute("md5Secret"); + String sha256Secret = (String) request.getSession().getAttribute("sha256Secret"); + + if (answer_pwd1!=null && answer_pwd2 !=null) { + if (answer_pwd1.equals(md5Secret) + && answer_pwd2.equals(sha256Secret)) { + return success(this) + .feedback("crypto-hashing.success") + .build(); + } else if (answer_pwd1.equals(md5Secret) + || answer_pwd2.equals(sha256Secret)) { + return failed(this).feedback("crypto-hashing.oneok").build(); + } + } + return failed(this).feedback("crypto-hashing.empty").build(); + } + + public static String getHash(String secret, String algorithm) throws NoSuchAlgorithmException { + MessageDigest md = MessageDigest.getInstance(algorithm); + md.update(secret.getBytes()); + byte[] digest = md.digest(); + return DatatypeConverter + .printHexBinary(digest).toUpperCase(); + } + +} diff --git a/tests/fixtures/sarif_convertor/shallow_sast_webgoat/IDORLogin.java b/tests/fixtures/sarif_convertor/shallow_sast_webgoat/IDORLogin.java new file mode 100644 index 00000000..7e0dd7ff --- /dev/null +++ b/tests/fixtures/sarif_convertor/shallow_sast_webgoat/IDORLogin.java @@ -0,0 +1,75 @@ +/* + * 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.idor; + +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.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.Map; + +@RestController +@AssignmentHints({"idor.hints.idor_login"}) +public class IDORLogin extends AssignmentEndpoint { + + private Map> idorUserInfo = new HashMap<>(); + + public void initIDORInfo() { + + idorUserInfo.put("tom", new HashMap()); + idorUserInfo.get("tom").put("password", "cat"); + idorUserInfo.get("tom").put("id", "2342384"); + idorUserInfo.get("tom").put("color", "yellow"); + idorUserInfo.get("tom").put("size", "small"); + + idorUserInfo.put("bill", new HashMap()); + idorUserInfo.get("bill").put("password", "buffalo"); + idorUserInfo.get("bill").put("id", "2342388"); + idorUserInfo.get("bill").put("color", "brown"); + idorUserInfo.get("bill").put("size", "large"); + + } + + @PostMapping("/IDOR/login") + @ResponseBody + public AttackResult completed(@RequestParam String username, @RequestParam String password) { + initIDORInfo(); + UserSessionData userSessionData = getUserSessionData(); + + if (idorUserInfo.containsKey(username)) { + if ("tom".equals(username) && idorUserInfo.get("tom").get("password").equals(password)) { + userSessionData.setValue("idor-authenticated-as", username); + userSessionData.setValue("idor-authenticated-user-id", idorUserInfo.get(username).get("id")); + return success(this).feedback("idor.login.success").feedbackArgs(username).build(); + } else { + return failed(this).feedback("idor.login.failure").build(); + } + } else { + return failed(this).feedback("idor.login.failure").build(); + } + } +} diff --git a/tests/fixtures/sarif_convertor/shallow_sast_webgoat/JWTFinalEndpointTest.java b/tests/fixtures/sarif_convertor/shallow_sast_webgoat/JWTFinalEndpointTest.java new file mode 100644 index 00000000..c79a1356 --- /dev/null +++ b/tests/fixtures/sarif_convertor/shallow_sast_webgoat/JWTFinalEndpointTest.java @@ -0,0 +1,98 @@ +package org.owasp.webgoat.jwt; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertTrue; +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 java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import org.hamcrest.CoreMatchers; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.owasp.webgoat.plugins.LessonTest; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import io.jsonwebtoken.Jwt; +import io.jsonwebtoken.Jwts; +import lombok.SneakyThrows; + +@RunWith(SpringJUnit4ClassRunner.class) +public class JWTFinalEndpointTest extends LessonTest { + + private static final String TOKEN_JERRY = "eyJraWQiOiJ3ZWJnb2F0X2tleSIsImFsZyI6IkhTNTEyIn0.eyJhdWQiOiJ3ZWJnb2F0Lm9yZyIsImVtYWlsIjoiamVycnlAd2ViZ29hdC5jb20iLCJ1c2VybmFtZSI6IkplcnJ5In0.xBc5FFwaOcuxjdr_VJ16n8Jb7vScuaZulNTl66F2MWF1aBe47QsUosvbjWGORNcMPiPNwnMu1Yb0WZVNrp2ZXA"; + + @Autowired + private JWT jwt; + + @Autowired + private JWTFinalEndpoint jwtFinalEndpoint; + + @Before + public void setup() { + when(webSession.getCurrentLesson()).thenReturn(jwt); + this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build(); + } + + @Test + public void solveAssignment() throws Exception { + String key = "deletingTom"; + Map claims = new HashMap<>(); + claims.put("username", "Tom"); + String token = Jwts.builder() + .setHeaderParam("kid", "hacked' UNION select '" + key + "' from INFORMATION_SCHEMA.SYSTEM_USERS --") + .setIssuedAt(new Date(System.currentTimeMillis() + TimeUnit.DAYS.toDays(10))) + .setClaims(claims) + .signWith(io.jsonwebtoken.SignatureAlgorithm.HS512, key) + .compact(); + mockMvc.perform(MockMvcRequestBuilders.post("/JWT/final/delete") + .param("token", token) + .content("")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.lessonCompleted", is(true))); + } + + @Test + public void withJerrysKeyShouldNotSolveAssignment() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.post("/JWT/final/delete") + .param("token", TOKEN_JERRY) + .content("")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.feedback", CoreMatchers.is(messages.getMessage("jwt-final-jerry-account")))); + } + + @Test + public void shouldNotBeAbleToBypassWithSimpleToken() throws Exception { + mockMvc.perform(MockMvcRequestBuilders.post("/JWT/final/delete") + .param("token", ".eyJ1c2VybmFtZSI6IlRvbSJ9.") + .content("")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.feedback", CoreMatchers.is(messages.getMessage("jwt-invalid-token")))); + } + + @Test + @SneakyThrows + public void testJWTTestTools() { + + //JWTFinalEndpoint jwtFinalEndpoint = new JWTFinalEndpoint(null); + String jsonHeader = "{\"alg\":\"HS256\"}"; + String jsonPayload = "{\"iss\":\"OWASP\"}"; + String jsonSecret = "secret"; + String jwtToken = jwtFinalEndpoint.encode(jsonHeader, jsonPayload, jsonSecret).replace(":", "") + .replace("encodedHeader", "").replace("encodedPayload", "").replace("encodedSignature", "") + .replace("{", "").replace("}", "").replace("\"", "").replace(",", "."); + + Jwt jwt = Jwts.parser().setSigningKey(jsonSecret).parse(jwtToken); + String revert = jwtFinalEndpoint.decode(jwtToken); + //System.out.println("revert: "+revert); + + } +} \ No newline at end of file diff --git a/tests/fixtures/sarif_convertor/shallow_sast_webgoat/JWTRefreshEndpoint.java b/tests/fixtures/sarif_convertor/shallow_sast_webgoat/JWTRefreshEndpoint.java new file mode 100644 index 00000000..39b63c32 --- /dev/null +++ b/tests/fixtures/sarif_convertor/shallow_sast_webgoat/JWTRefreshEndpoint.java @@ -0,0 +1,133 @@ +/* + * 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.jwt; + +import io.jsonwebtoken.*; +import org.apache.commons.lang3.RandomStringUtils; +import org.owasp.webgoat.assignments.AssignmentEndpoint; +import org.owasp.webgoat.assignments.AssignmentHints; +import org.owasp.webgoat.assignments.AttackResult; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.*; +import java.util.concurrent.TimeUnit; + +import static org.springframework.http.ResponseEntity.ok; + +/** + * @author nbaars + * @since 4/23/17. + */ +@RestController +@AssignmentHints({"jwt-refresh-hint1", "jwt-refresh-hint2", "jwt-refresh-hint3", "jwt-refresh-hint4"}) +public class JWTRefreshEndpoint extends AssignmentEndpoint { + + public static final String PASSWORD = "bm5nhSkxCXZkKRy4"; + private static final String JWT_PASSWORD = "bm5n3SkxCX4kKRy4"; + private static final List validRefreshTokens = new ArrayList<>(); + + @PostMapping(value = "/JWT/refresh/login", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) + @ResponseBody + public ResponseEntity follow(@RequestBody(required = false) Map json) { + if (json == null) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + } + String user = (String) json.get("user"); + String password = (String) json.get("password"); + + if ("Jerry".equalsIgnoreCase(user) && PASSWORD.equals(password)) { + return ok(createNewTokens(user)); + } + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + } + + private Map createNewTokens(String user) { + Map claims = new HashMap<>(); + claims.put("admin", "false"); + claims.put("user", user); + String token = Jwts.builder() + .setIssuedAt(new Date(System.currentTimeMillis() + TimeUnit.DAYS.toDays(10))) + .setClaims(claims) + .signWith(io.jsonwebtoken.SignatureAlgorithm.HS512, JWT_PASSWORD) + .compact(); + Map tokenJson = new HashMap<>(); + String refreshToken = RandomStringUtils.randomAlphabetic(20); + validRefreshTokens.add(refreshToken); + tokenJson.put("access_token", token); + tokenJson.put("refresh_token", refreshToken); + return tokenJson; + } + + @PostMapping("/JWT/refresh/checkout") + @ResponseBody + public ResponseEntity checkout(@RequestHeader(value = "Authorization", required = false) String token) { + if (token == null) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + } + try { + Jwt jwt = Jwts.parser().setSigningKey(JWT_PASSWORD).parse(token.replace("Bearer ", "")); + Claims claims = (Claims) jwt.getBody(); + String user = (String) claims.get("user"); + if ("Tom".equals(user)) { + return ok(success(this).build()); + } + return ok(failed(this).feedback("jwt-refresh-not-tom").feedbackArgs(user).build()); + } catch (ExpiredJwtException e) { + return ok(failed(this).output(e.getMessage()).build()); + } catch (JwtException e) { + return ok(failed(this).feedback("jwt-invalid-token").build()); + } + } + + @PostMapping("/JWT/refresh/newToken") + @ResponseBody + public ResponseEntity newToken(@RequestHeader(value = "Authorization", required = false) String token, + @RequestBody(required = false) Map json) { + if (token == null || json == null) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + } + + String user; + String refreshToken; + try { + Jwt jwt = Jwts.parser().setSigningKey(JWT_PASSWORD).parse(token.replace("Bearer ", "")); + user = (String) jwt.getBody().get("user"); + refreshToken = (String) json.get("refresh_token"); + } catch (ExpiredJwtException e) { + user = (String) e.getClaims().get("user"); + refreshToken = (String) json.get("refresh_token"); + } + + if (user == null || refreshToken == null) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + } else if (validRefreshTokens.contains(refreshToken)) { + validRefreshTokens.remove(refreshToken); + return ok(createNewTokens(user)); + } else { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + } + } +} diff --git a/tests/fixtures/sarif_convertor/shallow_sast_webgoat/JWTVotesEndpoint.java b/tests/fixtures/sarif_convertor/shallow_sast_webgoat/JWTVotesEndpoint.java new file mode 100644 index 00000000..cf36551b --- /dev/null +++ b/tests/fixtures/sarif_convertor/shallow_sast_webgoat/JWTVotesEndpoint.java @@ -0,0 +1,177 @@ +/* + * 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.jwt; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwt; +import io.jsonwebtoken.JwtException; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.impl.TextCodec; +import org.apache.commons.lang3.StringUtils; +import org.owasp.webgoat.assignments.AssignmentEndpoint; +import org.owasp.webgoat.assignments.AssignmentHints; +import org.owasp.webgoat.assignments.AttackResult; +import org.owasp.webgoat.jwt.votes.Views; +import org.owasp.webgoat.jwt.votes.Vote; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.json.MappingJacksonValue; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.PostConstruct; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletResponse; +import java.time.Duration; +import java.time.Instant; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import static java.util.Comparator.comparingLong; +import static java.util.Optional.ofNullable; +import static java.util.stream.Collectors.toList; + +/** + * @author nbaars + * @since 4/23/17. + */ +@RestController +@AssignmentHints({"jwt-change-token-hint1", "jwt-change-token-hint2", "jwt-change-token-hint3", "jwt-change-token-hint4", "jwt-change-token-hint5"}) +public class JWTVotesEndpoint extends AssignmentEndpoint { + + public static final String JWT_PASSWORD = TextCodec.BASE64.encode("victory"); + private static String validUsers = "TomJerrySylvester"; + + private static int totalVotes = 38929; + private Map votes = new HashMap<>(); + + @PostConstruct + public void initVotes() { + votes.put("Admin lost password", new Vote("Admin lost password", + "In this challenge you will need to help the admin and find the password in order to login", + "challenge1-small.png", "challenge1.png", 36000, totalVotes)); + votes.put("Vote for your favourite", + new Vote("Vote for your favourite", + "In this challenge ...", + "challenge5-small.png", "challenge5.png", 30000, totalVotes)); + votes.put("Get it for free", + new Vote("Get it for free", + "The objective for this challenge is to buy a Samsung phone for free.", + "challenge2-small.png", "challenge2.png", 20000, totalVotes)); + votes.put("Photo comments", + new Vote("Photo comments", + "n this challenge you can comment on the photo you will need to find the flag somewhere.", + "challenge3-small.png", "challenge3.png", 10000, totalVotes)); + } + + @GetMapping("/JWT/votings/login") + public void login(@RequestParam("user") String user, HttpServletResponse response) { + if (validUsers.contains(user)) { + Claims claims = Jwts.claims().setIssuedAt(Date.from(Instant.now().plus(Duration.ofDays(10)))); + claims.put("admin", "false"); + claims.put("user", user); + String token = Jwts.builder() + .setClaims(claims) + .signWith(io.jsonwebtoken.SignatureAlgorithm.HS512, JWT_PASSWORD) + .compact(); + Cookie cookie = new Cookie("access_token", token); + response.addCookie(cookie); + response.setStatus(HttpStatus.OK.value()); + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + } else { + Cookie cookie = new Cookie("access_token", ""); + response.addCookie(cookie); + response.setStatus(HttpStatus.UNAUTHORIZED.value()); + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + } + } + + @GetMapping("/JWT/votings") + @ResponseBody + public MappingJacksonValue getVotes(@CookieValue(value = "access_token", required = false) String accessToken) { + MappingJacksonValue value = new MappingJacksonValue(votes.values().stream().sorted(comparingLong(Vote::getAverage).reversed()).collect(toList())); + if (StringUtils.isEmpty(accessToken)) { + value.setSerializationView(Views.GuestView.class); + } else { + try { + Jwt jwt = Jwts.parser().setSigningKey(JWT_PASSWORD).parse(accessToken); + Claims claims = (Claims) jwt.getBody(); + String user = (String) claims.get("user"); + if ("Guest".equals(user) || !validUsers.contains(user)) { + value.setSerializationView(Views.GuestView.class); + } else { + value.setSerializationView(Views.UserView.class); + } + } catch (JwtException e) { + value.setSerializationView(Views.GuestView.class); + } + } + return value; + } + + @PostMapping(value = "/JWT/votings/{title}") + @ResponseBody + @ResponseStatus(HttpStatus.ACCEPTED) + public ResponseEntity vote(@PathVariable String title, @CookieValue(value = "access_token", required = false) String accessToken) { + if (StringUtils.isEmpty(accessToken)) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + } else { + try { + Jwt jwt = Jwts.parser().setSigningKey(JWT_PASSWORD).parse(accessToken); + Claims claims = (Claims) jwt.getBody(); + String user = (String) claims.get("user"); + if (!validUsers.contains(user)) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + } else { + ofNullable(votes.get(title)).ifPresent(v -> v.incrementNumberOfVotes(totalVotes)); + return ResponseEntity.accepted().build(); + } + } catch (JwtException e) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + } + } + } + + @PostMapping("/JWT/votings") + @ResponseBody + public AttackResult resetVotes(@CookieValue(value = "access_token", required = false) String accessToken) { + if (StringUtils.isEmpty(accessToken)) { + return failed(this).feedback("jwt-invalid-token").build(); + } else { + try { + Jwt jwt = Jwts.parser().setSigningKey(JWT_PASSWORD).parse(accessToken); + Claims claims = (Claims) jwt.getBody(); + boolean isAdmin = Boolean.valueOf((String) claims.get("admin")); + if (!isAdmin) { + return failed(this).feedback("jwt-only-admin").build(); + } else { + votes.values().forEach(vote -> vote.reset()); + return success(this).build(); + } + } catch (JwtException e) { + return failed(this).feedback("jwt-invalid-token").output(e.toString()).build(); + } + } + } +} diff --git a/tests/fixtures/sarif_convertor/shallow_sast_webgoat/LICENSE.txt b/tests/fixtures/sarif_convertor/shallow_sast_webgoat/LICENSE.txt new file mode 100644 index 00000000..573d2b4e --- /dev/null +++ b/tests/fixtures/sarif_convertor/shallow_sast_webgoat/LICENSE.txt @@ -0,0 +1,19 @@ +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. \ No newline at end of file diff --git a/tests/fixtures/sarif_convertor/shallow_sast_webgoat/SSRFTask2.java b/tests/fixtures/sarif_convertor/shallow_sast_webgoat/SSRFTask2.java new file mode 100644 index 00000000..533fd791 --- /dev/null +++ b/tests/fixtures/sarif_convertor/shallow_sast_webgoat/SSRFTask2.java @@ -0,0 +1,82 @@ +/* + * 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.ssrf; + +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.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.URL; +import java.net.URLConnection; + + +@RestController +@AssignmentHints({"ssrf.hint3"}) +public class SSRFTask2 extends AssignmentEndpoint { + + @PostMapping("/SSRF/task2") + @ResponseBody + public AttackResult completed(@RequestParam String url) { + return furBall(url); + } + + protected AttackResult furBall(String url) { + try { + StringBuffer html = new StringBuffer(); + + if (url.matches("http://ifconfig.pro")) { + URL u = new URL(url); + URLConnection urlConnection = u.openConnection(); + BufferedReader in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream())); + String inputLine; + + while ((inputLine = in.readLine()) != null) { + html.append(inputLine); + } + in.close(); + + return success(this) + .feedback("ssrf.success") + .output(html.toString()) + .build(); + } else { + html.append("\"image"); + return failed(this) + .feedback("ssrf.failure") + .output(html.toString()) + .build(); + } + } catch (Exception e) { + e.printStackTrace(); + return failed(this) + .output(e.getMessage()) + .build(); + } + } +} diff --git a/tests/fixtures/sarif_convertor/shallow_sast_webgoat/SqlInjectionLesson4.java b/tests/fixtures/sarif_convertor/shallow_sast_webgoat/SqlInjectionLesson4.java new file mode 100644 index 00000000..e0e8286f --- /dev/null +++ b/tests/fixtures/sarif_convertor/shallow_sast_webgoat/SqlInjectionLesson4.java @@ -0,0 +1,77 @@ + +/* + * 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.sql_injection.introduction; + +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.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +import javax.sql.DataSource; +import java.sql.*; + +import static java.sql.ResultSet.*; + + +@RestController +@AssignmentHints(value = {"SqlStringInjectionHint4-1", "SqlStringInjectionHint4-2", "SqlStringInjectionHint4-3"}) +public class SqlInjectionLesson4 extends AssignmentEndpoint { + + private final DataSource dataSource; + + public SqlInjectionLesson4(DataSource dataSource) { + this.dataSource = dataSource; + } + + @PostMapping("/SqlInjection/attack4") + @ResponseBody + public AttackResult completed(@RequestParam String query) { + return injectableQuery(query); + } + + protected AttackResult injectableQuery(String query) { + try (Connection connection = dataSource.getConnection()) { + try (Statement statement = connection.createStatement(TYPE_SCROLL_INSENSITIVE, CONCUR_READ_ONLY)) { + statement.executeUpdate(query); + connection.commit(); + ResultSet results = statement.executeQuery("SELECT phone from employees;"); + StringBuffer output = new StringBuffer(); + // user completes lesson if column phone exists + if (results.first()) { + output.append(""); + return success(this).output(output.toString()).build(); + } else { + return failed(this).output(output.toString()).build(); + } + } catch (SQLException sqle) { + return failed(this).output(sqle.getMessage()).build(); + } + } catch (Exception e) { + return failed(this).output(this.getClass().getName() + " : " + e.getMessage()).build(); + } + } +} diff --git a/tests/fixtures/sarif_convertor/shallow_sast_webgoat/SqlInjectionLesson6a.java b/tests/fixtures/sarif_convertor/shallow_sast_webgoat/SqlInjectionLesson6a.java new file mode 100644 index 00000000..7220fee5 --- /dev/null +++ b/tests/fixtures/sarif_convertor/shallow_sast_webgoat/SqlInjectionLesson6a.java @@ -0,0 +1,99 @@ +/* + * 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.sql_injection.advanced; + +import org.owasp.webgoat.assignments.AssignmentEndpoint; +import org.owasp.webgoat.assignments.AssignmentHints; +import org.owasp.webgoat.assignments.AttackResult; +import org.owasp.webgoat.sql_injection.introduction.SqlInjectionLesson5a; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +import javax.sql.DataSource; +import java.sql.*; + + +@RestController +@AssignmentHints(value = {"SqlStringInjectionHint-advanced-6a-1", "SqlStringInjectionHint-advanced-6a-2", "SqlStringInjectionHint-advanced-6a-3", + "SqlStringInjectionHint-advanced-6a-4"}) +public class SqlInjectionLesson6a extends AssignmentEndpoint { + + private final DataSource dataSource; + + public SqlInjectionLesson6a(DataSource dataSource) { + this.dataSource = dataSource; + } + + @PostMapping("/SqlInjectionAdvanced/attack6a") + @ResponseBody + public AttackResult completed(@RequestParam String userid_6a) { + return injectableQuery(userid_6a); + // The answer: Smith' union select userid,user_name, password,cookie,cookie, cookie,userid from user_system_data -- + } + + public AttackResult injectableQuery(String accountName) { + String query = ""; + try (Connection connection = dataSource.getConnection()) { + boolean usedUnion = true; + query = "SELECT * FROM user_data WHERE last_name = '" + accountName + "'"; + //Check if Union is used + if (!accountName.matches("(?i)(^[^-/*;)]*)(\\s*)UNION(.*$)")) { + usedUnion = false; + } + try (Statement statement = connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, + ResultSet.CONCUR_READ_ONLY)) { + ResultSet results = statement.executeQuery(query); + + if ((results != null) && (results.first())) { + ResultSetMetaData resultsMetaData = results.getMetaData(); + StringBuffer output = new StringBuffer(); + + output.append(SqlInjectionLesson5a.writeTable(results, resultsMetaData)); + + String appendingWhenSucceded; + if (usedUnion) + appendingWhenSucceded = "Well done! Can you also figure out a solution, by appending a new Sql Statement?"; + else + appendingWhenSucceded = "Well done! Can you also figure out a solution, by using a UNION?"; + results.last(); + + if (output.toString().contains("dave") && output.toString().contains("passW0rD")) { + output.append(appendingWhenSucceded); + return success(this).feedback("sql-injection.advanced.6a.success").feedbackArgs(output.toString()).output(" Your query was: " + query).build(); + } else { + return failed(this).output(output.toString() + "
Your query was: " + query).build(); + } + } else { + return failed(this).feedback("sql-injection.advanced.6a.no.results").output(" Your query was: " + query).build(); + } + } catch (SQLException sqle) { + return failed(this).output(sqle.getMessage() + "
Your query was: " + query).build(); + } + } catch (Exception e) { + e.printStackTrace(); + return failed(this).output(this.getClass().getName() + " : " + e.getMessage() + "
Your query was: " + query).build(); + } + } +} \ No newline at end of file diff --git a/tests/fixtures/sarif_convertor/shallow_sast_webgoat/SqlInjectionLesson9.java b/tests/fixtures/sarif_convertor/shallow_sast_webgoat/SqlInjectionLesson9.java new file mode 100644 index 00000000..2fd36428 --- /dev/null +++ b/tests/fixtures/sarif_convertor/shallow_sast_webgoat/SqlInjectionLesson9.java @@ -0,0 +1,110 @@ + +/* + * 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.sql_injection.introduction; + +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.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +import static org.hsqldb.jdbc.JDBCResultSet.CONCUR_UPDATABLE; +import static org.hsqldb.jdbc.JDBCResultSet.TYPE_SCROLL_SENSITIVE; + +@RestController +@AssignmentHints(value = {"SqlStringInjectionHint.9.1", "SqlStringInjectionHint.9.2", "SqlStringInjectionHint.9.3", "SqlStringInjectionHint.9.4", "SqlStringInjectionHint.9.5"}) +public class SqlInjectionLesson9 extends AssignmentEndpoint { + + private final DataSource dataSource; + + public SqlInjectionLesson9(DataSource dataSource) { + this.dataSource = dataSource; + } + + @PostMapping("/SqlInjection/attack9") + @ResponseBody + public AttackResult completed(@RequestParam String name, @RequestParam String auth_tan) { + return injectableQueryIntegrity(name, auth_tan); + } + + protected AttackResult injectableQueryIntegrity(String name, String auth_tan) { + StringBuffer output = new StringBuffer(); + String query = "SELECT * FROM employees WHERE last_name = '" + name + "' AND auth_tan = '" + auth_tan + "'"; + try (Connection connection = dataSource.getConnection()) { + try { + Statement statement = connection.createStatement(TYPE_SCROLL_SENSITIVE, CONCUR_UPDATABLE); + SqlInjectionLesson8.log(connection, query); + ResultSet results = statement.executeQuery(query); + var test = results.getRow() != 0; + if (results.getStatement() != null) { + if (results.first()) { + output.append(SqlInjectionLesson8.generateTable(results)); + } else { + // no results + return failed(this).feedback("sql-injection.8.no.results").build(); + } + } + } catch (SQLException e) { + System.err.println(e.getMessage()); + return failed(this).feedback("sql-injection.error").output("
").build(); + } + + return checkSalaryRanking(connection, output); + + } catch (Exception e) { + System.err.println(e.getMessage()); + return failed(this).feedback("sql-injection.error").output("
").build(); + } + } + + private AttackResult checkSalaryRanking(Connection connection, StringBuffer output) { + try { + String query = "SELECT * FROM employees ORDER BY salary DESC"; + try (Statement statement = connection.createStatement(TYPE_SCROLL_SENSITIVE, CONCUR_UPDATABLE); + ) { + ResultSet results = statement.executeQuery(query); + + results.first(); + // user completes lesson if John Smith is the first in the list + if ((results.getString(2).equals("John")) && (results.getString(3).equals("Smith"))) { + output.append(SqlInjectionLesson8.generateTable(results)); + return success(this).feedback("sql-injection.9.success").output(output.toString()).build(); + } else { + return failed(this).feedback("sql-injection.9.one").output(output.toString()).build(); + } + } + } catch (SQLException e) { + return failed(this).feedback("sql-injection.error").output("
").build(); + } + } + +} diff --git a/tests/fixtures/sarif_convertor/shallow_sast_webgoat/SqlOnlyInputValidation.java b/tests/fixtures/sarif_convertor/shallow_sast_webgoat/SqlOnlyInputValidation.java new file mode 100644 index 00000000..0bfa5baf --- /dev/null +++ b/tests/fixtures/sarif_convertor/shallow_sast_webgoat/SqlOnlyInputValidation.java @@ -0,0 +1,55 @@ + +/* + * 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.sql_injection.mitigation; + +import org.owasp.webgoat.assignments.AssignmentEndpoint; +import org.owasp.webgoat.assignments.AssignmentHints; +import org.owasp.webgoat.assignments.AttackResult; +import org.owasp.webgoat.sql_injection.advanced.SqlInjectionLesson6a; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + + +@RestController +@AssignmentHints(value = {"SqlOnlyInputValidation-1", "SqlOnlyInputValidation-2", "SqlOnlyInputValidation-3"}) +public class SqlOnlyInputValidation extends AssignmentEndpoint { + + private final SqlInjectionLesson6a lesson6a; + + public SqlOnlyInputValidation(SqlInjectionLesson6a lesson6a) { + this.lesson6a = lesson6a; + } + + @PostMapping("/SqlOnlyInputValidation/attack") + @ResponseBody + public AttackResult attack(@RequestParam("userid_sql_only_input_validation") String userId) { + if (userId.contains(" ")) { + return failed(this).feedback("SqlOnlyInputValidation-failed").build(); + } + AttackResult attackResult = lesson6a.injectableQuery(userId); + return new AttackResult(attackResult.isLessonCompleted(), attackResult.getFeedback(), attackResult.getOutput(), getClass().getSimpleName(), true); + } +} diff --git a/tests/fixtures/sarif_convertor/shallow_sast_webgoat/StoredXssComments.java b/tests/fixtures/sarif_convertor/shallow_sast_webgoat/StoredXssComments.java new file mode 100644 index 00000000..af96c4fe --- /dev/null +++ b/tests/fixtures/sarif_convertor/shallow_sast_webgoat/StoredXssComments.java @@ -0,0 +1,105 @@ +/* + * 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.xss.stored; + +import com.beust.jcommander.internal.Lists; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.joda.time.DateTime; +import org.joda.time.format.DateTimeFormat; +import org.joda.time.format.DateTimeFormatter; +import org.owasp.webgoat.assignments.AssignmentEndpoint; +import org.owasp.webgoat.assignments.AttackResult; +import org.owasp.webgoat.session.WebSession; +import org.owasp.webgoat.xss.Comment; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.*; + +import static org.springframework.http.MediaType.ALL_VALUE; + +import java.io.IOException; +import java.util.*; + + +@RestController +public class StoredXssComments extends AssignmentEndpoint { + + @Autowired + private WebSession webSession; + private static DateTimeFormatter fmt = DateTimeFormat.forPattern("yyyy-MM-dd, HH:mm:ss"); + + private static final Map> userComments = new HashMap<>(); + private static final List comments = new ArrayList<>(); + private static final String phoneHomeString = ""; + + + static { + comments.add(new Comment("secUriTy", DateTime.now().toString(fmt), "Comment for Unit Testing")); + comments.add(new Comment("webgoat", DateTime.now().toString(fmt), "This comment is safe")); + comments.add(new Comment("guest", DateTime.now().toString(fmt), "This one is safe too.")); + comments.add(new Comment("guest", DateTime.now().toString(fmt), "Can you post a comment, calling webgoat.customjs.phoneHome() ?")); + } + + //TODO This assignment seems not to be in use in the UI + @GetMapping(path = "/CrossSiteScriptingStored/stored-xss", produces = MediaType.APPLICATION_JSON_VALUE, consumes = ALL_VALUE) + @ResponseBody + public Collection retrieveComments() { + List allComments = Lists.newArrayList(); + Collection newComments = userComments.get(webSession.getUserName()); + allComments.addAll(comments); + if (newComments != null) { + allComments.addAll(newComments); + } + Collections.reverse(allComments); + return allComments; + } + + //TODO This assignment seems not to be in use in the UI + @PostMapping("/CrossSiteScriptingStored/stored-xss") + @ResponseBody + public AttackResult createNewComment(@RequestBody String commentStr) { + Comment comment = parseJson(commentStr); + + List comments = userComments.getOrDefault(webSession.getUserName(), new ArrayList<>()); + comment.setDateTime(DateTime.now().toString(fmt)); + comment.setUser(webSession.getUserName()); + + comments.add(comment); + userComments.put(webSession.getUserName(), comments); + + if (comment.getText().contains(phoneHomeString)) { + return (success(this).feedback("xss-stored-comment-success").build()); + } else { + return (failed(this).feedback("xss-stored-comment-failure").build()); + } + } + + private Comment parseJson(String comment) { + ObjectMapper mapper = new ObjectMapper(); + try { + return mapper.readValue(comment, Comment.class); + } catch (IOException e) { + return new Comment(); + } + } +} \ No newline at end of file diff --git a/tests/fixtures/sarif_convertor/shallow_sast_webgoat/UserValidatorTest.java b/tests/fixtures/sarif_convertor/shallow_sast_webgoat/UserValidatorTest.java new file mode 100644 index 00000000..f88a50e4 --- /dev/null +++ b/tests/fixtures/sarif_convertor/shallow_sast_webgoat/UserValidatorTest.java @@ -0,0 +1,61 @@ +package org.owasp.webgoat.users; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.validation.BeanPropertyBindingResult; +import org.springframework.validation.Errors; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class UserValidatorTest { + + @Mock + private UserRepository userRepository; + + @Test + public void passwordsShouldMatch() { + UserForm userForm = new UserForm(); + userForm.setAgree("true"); + userForm.setUsername("test1234"); + userForm.setPassword("test1234"); + userForm.setMatchingPassword("test1234"); + Errors errors = new BeanPropertyBindingResult(userForm, "userForm"); + new UserValidator(userRepository).validate(userForm, errors); + assertFalse(errors.hasErrors()); + } + + @Test + public void shouldGiveErrorWhenPasswordsDoNotMatch() { + UserForm userForm = new UserForm(); + userForm.setAgree("true"); + userForm.setUsername("test1234"); + userForm.setPassword("test12345"); + userForm.setMatchingPassword("test1234"); + Errors errors = new BeanPropertyBindingResult(userForm, "userForm"); + new UserValidator(userRepository).validate(userForm, errors); + assertTrue(errors.hasErrors()); + assertThat(errors.getFieldError("matchingPassword").getCode()).isEqualTo("password.diff"); + } + + @Test + public void shouldGiveErrorWhenUserAlreadyExists() { + UserForm userForm = new UserForm(); + userForm.setAgree("true"); + userForm.setUsername("test12345"); + userForm.setPassword("test12345"); + userForm.setMatchingPassword("test12345"); + when(userRepository.findByUsername(anyString())).thenReturn(new WebGoatUser("test1245", "password")); + Errors errors = new BeanPropertyBindingResult(userForm, "userForm"); + new UserValidator(userRepository).validate(userForm, errors); + assertTrue(errors.hasErrors()); + assertThat(errors.getFieldError("username").getCode()).isEqualTo("username.duplicate"); + } + +} \ No newline at end of file diff --git a/tests/fixtures/sarif_convertor/shallow_sast_webgoat/Users.java b/tests/fixtures/sarif_convertor/shallow_sast_webgoat/Users.java new file mode 100644 index 00000000..291ee9c8 --- /dev/null +++ b/tests/fixtures/sarif_convertor/shallow_sast_webgoat/Users.java @@ -0,0 +1,117 @@ +/* + * 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.session.UserSessionData; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +import javax.sql.DataSource; +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 DataSource dataSource; + + public Users(UserSessionData userSessionData, DataSource dataSource) { + this.userSessionData = userSessionData; + this.dataSource = dataSource; + } + + @GetMapping(produces = {"application/json"}) + @ResponseBody + protected 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 allUsersMap = new HashMap(); + + if ((results != null) && (results.first() == true)) { + while (results.next()) { + HashMap 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 errMap = new HashMap() {{ + put("err", sqle.getErrorCode() + "::" + sqle.getMessage()); + }}; + + return new HashMap() {{ + put(0, errMap); + }}; + } catch (Exception e) { + e.printStackTrace(); + HashMap errMap = new HashMap() {{ + put("err", e.getMessage() + "::" + e.getCause()); + }}; + e.printStackTrace(); + return new HashMap() {{ + put(0, errMap); + }}; + + + } finally { + try { + if (connection != null) { + connection.close(); + } + } catch (SQLException sqle) { + sqle.printStackTrace(); + } + } + + } catch (Exception e) { + e.printStackTrace(); + HashMap errMap = new HashMap() {{ + put("err", e.getMessage() + "::" + e.getCause()); + }}; + e.printStackTrace(); + return new HashMap<>() {{ + put(0, errMap); + }}; + + } + return null; + } +} diff --git a/tests/fixtures/sarif_convertor/shallow_sast_webgoat/WebSecurityConfig.java b/tests/fixtures/sarif_convertor/shallow_sast_webgoat/WebSecurityConfig.java new file mode 100644 index 00000000..79a1b075 --- /dev/null +++ b/tests/fixtures/sarif_convertor/shallow_sast_webgoat/WebSecurityConfig.java @@ -0,0 +1,100 @@ +/** + * ************************************************************************************************ + * This file is part of WebGoat, an Open Web Application Security Project utility. For details, + * please see http://www.owasp.org/ + *

+ * Copyright (c) 2002 - 20014 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. + *

+ * + * @author WebGoat + * @version $Id: $Id + * @since December 12, 2015 + */ + +package org.owasp.webgoat; + +import lombok.AllArgsConstructor; +import org.owasp.webgoat.users.UserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.password.NoOpPasswordEncoder; + +/** + * Security configuration for WebGoat. + */ +@Configuration +@AllArgsConstructor +@EnableWebSecurity +public class WebSecurityConfig extends WebSecurityConfigurerAdapter { + + private final UserService userDetailsService; + + @Override + protected void configure(HttpSecurity http) throws Exception { + ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry security = http + .authorizeRequests() + .antMatchers("/css/**", "/images/**", "/js/**", "fonts/**", "/plugins/**", "/registration", "/register.mvc").permitAll() + .anyRequest().authenticated(); + security.and() + .formLogin() + .loginPage("/login") + .defaultSuccessUrl("/welcome.mvc", true) + .usernameParameter("username") + .passwordParameter("password") + .permitAll(); + security.and() + .logout().deleteCookies("JSESSIONID").invalidateHttpSession(true); + security.and().csrf().disable(); + + http.headers().cacheControl().disable(); + http.exceptionHandling().authenticationEntryPoint(new AjaxAuthenticationEntryPoint("/login")); + } + + @Autowired + public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { + auth.userDetailsService(userDetailsService); //.passwordEncoder(bCryptPasswordEncoder()); + } + + @Bean + @Override + public UserDetailsService userDetailsServiceBean() throws Exception { + return userDetailsService; + } + + @Override + @Bean + protected AuthenticationManager authenticationManager() throws Exception { + return super.authenticationManager(); + } + + @SuppressWarnings("deprecation") + @Bean + public NoOpPasswordEncoder passwordEncoder() { + return (NoOpPasswordEncoder) NoOpPasswordEncoder.getInstance(); + } +} \ No newline at end of file diff --git a/tests/fixtures/sarif_convertor/shallow_sast_webgoat/jquery.form.js b/tests/fixtures/sarif_convertor/shallow_sast_webgoat/jquery.form.js new file mode 100644 index 00000000..bced6cbe --- /dev/null +++ b/tests/fixtures/sarif_convertor/shallow_sast_webgoat/jquery.form.js @@ -0,0 +1,1367 @@ +/*! + * jQuery Form Plugin + * version: 3.51.0-2014.06.20 + * Requires jQuery v1.5 or later + * Copyright (c) 2014 M. Alsup + * Examples and documentation at: http://malsup.com/jquery/form/ + * Project repository: https://github.com/malsup/form + * Dual licensed under the MIT and GPL licenses. + * https://github.com/malsup/form#copyright-and-license + */ +/*global ActiveXObject */ + +// AMD support +(function (factory) { + "use strict"; + if (typeof define === "function" && define.amd) { + // using AMD; register as anon module + define(["jquery"], factory); + } else { + // no AMD; invoke directly + factory(typeof jQuery != "undefined" ? jQuery : window.Zepto); + } +})(function ($) { + "use strict"; + + /* + Usage Note: + ----------- + Do not use both ajaxSubmit and ajaxForm on the same form. These + functions are mutually exclusive. Use ajaxSubmit if you want + to bind your own submit handler to the form. For example, + + $(document).ready(function() { + $('#myForm').on('submit', function(e) { + e.preventDefault(); // <-- important + $(this).ajaxSubmit({ + target: '#output' + }); + }); + }); + + Use ajaxForm when you want the plugin to manage all the event binding + for you. For example, + + $(document).ready(function() { + $('#myForm').ajaxForm({ + target: '#output' + }); + }); + + You can also use ajaxForm with delegation (requires jQuery v1.7+), so the + form does not have to exist when you invoke ajaxForm: + + $('#myForm').ajaxForm({ + delegation: true, + target: '#output' + }); + + When using ajaxForm, the ajaxSubmit function will be invoked for you + at the appropriate time. +*/ + + /** + * Feature detection + */ + var feature = {}; + feature.fileapi = $("").get(0).files !== undefined; + feature.formdata = window.FormData !== undefined; + + var hasProp = !!$.fn.prop; + + // attr2 uses prop when it can but checks the return type for + // an expected string. this accounts for the case where a form + // contains inputs with names like "action" or "method"; in those + // cases "prop" returns the element + $.fn.attr2 = function () { + if (!hasProp) { + return this.attr.apply(this, arguments); + } + var val = this.prop.apply(this, arguments); + if ((val && val.jquery) || typeof val === "string") { + return val; + } + return this.attr.apply(this, arguments); + }; + + /** + * ajaxSubmit() provides a mechanism for immediately submitting + * an HTML form using AJAX. + */ + $.fn.ajaxSubmit = function (options) { + /*jshint scripturl:true */ + + // fast fail if nothing selected (http://dev.jquery.com/ticket/2752) + if (!this.length) { + log("ajaxSubmit: skipping submit process - no element selected"); + return this; + } + + var method, + action, + url, + $form = this; + + if (typeof options == "function") { + options = { success: options }; + } else if (options === undefined) { + options = {}; + } + + method = options.type || this.attr2("method"); + action = options.url || this.attr2("action"); + + url = typeof action === "string" ? $.trim(action) : ""; + url = url || window.location.href || ""; + if (url) { + // clean url (don't include hash vaue) + url = (url.match(/^([^#]+)/) || [])[1]; + } + + options = $.extend( + true, + { + url: url, + success: $.ajaxSettings.success, + type: method || $.ajaxSettings.type, + iframeSrc: /^https/i.test(window.location.href || "") + ? "javascript:false" + : "about:blank", + }, + options + ); + + // hook for manipulating the form data before it is extracted; + // convenient for use with rich editors like tinyMCE or FCKEditor + var veto = {}; + this.trigger("form-pre-serialize", [this, options, veto]); + if (veto.veto) { + log("ajaxSubmit: submit vetoed via form-pre-serialize trigger"); + return this; + } + + // provide opportunity to alter form data before it is serialized + if ( + options.beforeSerialize && + options.beforeSerialize(this, options) === false + ) { + log("ajaxSubmit: submit aborted via beforeSerialize callback"); + return this; + } + + var traditional = options.traditional; + if (traditional === undefined) { + traditional = $.ajaxSettings.traditional; + } + + var elements = []; + var qx, + a = this.formToArray(options.semantic, elements); + if (options.data) { + options.extraData = options.data; + qx = $.param(options.data, traditional); + } + + // give pre-submit callback an opportunity to abort the submit + if ( + options.beforeSubmit && + options.beforeSubmit(a, this, options) === false + ) { + log("ajaxSubmit: submit aborted via beforeSubmit callback"); + return this; + } + + // fire vetoable 'validate' event + this.trigger("form-submit-validate", [a, this, options, veto]); + if (veto.veto) { + log("ajaxSubmit: submit vetoed via form-submit-validate trigger"); + return this; + } + + var q = $.param(a, traditional); + if (qx) { + q = q ? q + "&" + qx : qx; + } + if (options.type.toUpperCase() == "GET") { + options.url += (options.url.indexOf("?") >= 0 ? "&" : "?") + q; + options.data = null; // data is null for 'get' + } else { + options.data = q; // data is the query string for 'post' + } + + var callbacks = []; + if (options.resetForm) { + callbacks.push(function () { + $form.resetForm(); + }); + } + if (options.clearForm) { + callbacks.push(function () { + $form.clearForm(options.includeHidden); + }); + } + + // perform a load on the target only if dataType is not provided + if (!options.dataType && options.target) { + var oldSuccess = options.success || function () {}; + callbacks.push(function (data) { + var fn = options.replaceTarget ? "replaceWith" : "html"; + $(options.target)[fn](data).each(oldSuccess, arguments); + }); + } else if (options.success) { + callbacks.push(options.success); + } + + options.success = function (data, status, xhr) { + // jQuery 1.4+ passes xhr as 3rd arg + var context = options.context || this; // jQuery 1.4+ supports scope context + for (var i = 0, max = callbacks.length; i < max; i++) { + callbacks[i].apply(context, [data, status, xhr || $form, $form]); + } + }; + + if (options.error) { + var oldError = options.error; + options.error = function (xhr, status, error) { + var context = options.context || this; + oldError.apply(context, [xhr, status, error, $form]); + }; + } + + if (options.complete) { + var oldComplete = options.complete; + options.complete = function (xhr, status) { + var context = options.context || this; + oldComplete.apply(context, [xhr, status, $form]); + }; + } + + // are there files to upload? + + // [value] (issue #113), also see comment: + // https://github.com/malsup/form/commit/588306aedba1de01388032d5f42a60159eea9228#commitcomment-2180219 + var fileInputs = $("input[type=file]:enabled", this).filter(function () { + return $(this).val() !== ""; + }); + + var hasFileInputs = fileInputs.length > 0; + var mp = "multipart/form-data"; + var multipart = $form.attr("enctype") == mp || $form.attr("encoding") == mp; + + var fileAPI = feature.fileapi && feature.formdata; + log("fileAPI :" + fileAPI); + var shouldUseFrame = (hasFileInputs || multipart) && !fileAPI; + + var jqxhr; + + // options.iframe allows user to force iframe mode + // 06-NOV-09: now defaulting to iframe mode if file input is detected + if (options.iframe !== false && (options.iframe || shouldUseFrame)) { + // hack to fix Safari hang (thanks to Tim Molendijk for this) + // see: http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d + if (options.closeKeepAlive) { + $.get(options.closeKeepAlive, function () { + jqxhr = fileUploadIframe(a); + }); + } else { + jqxhr = fileUploadIframe(a); + } + } else if ((hasFileInputs || multipart) && fileAPI) { + jqxhr = fileUploadXhr(a); + } else { + jqxhr = $.ajax(options); + } + + $form.removeData("jqxhr").data("jqxhr", jqxhr); + + // clear element array + for (var k = 0; k < elements.length; k++) { + elements[k] = null; + } + + // fire 'notify' event + this.trigger("form-submit-notify", [this, options]); + return this; + + // utility fn for deep serialization + function deepSerialize(extraData) { + var serialized = $.param(extraData, options.traditional).split("&"); + var len = serialized.length; + var result = []; + var i, part; + for (i = 0; i < len; i++) { + // #252; undo param space replacement + serialized[i] = serialized[i].replace(/\+/g, " "); + part = serialized[i].split("="); + // #278; use array instead of object storage, favoring array serializations + result.push([decodeURIComponent(part[0]), decodeURIComponent(part[1])]); + } + return result; + } + + // XMLHttpRequest Level 2 file uploads (big hat tip to francois2metz) + function fileUploadXhr(a) { + var formdata = new FormData(); + + for (var i = 0; i < a.length; i++) { + formdata.append(a[i].name, a[i].value); + } + + if (options.extraData) { + var serializedData = deepSerialize(options.extraData); + for (i = 0; i < serializedData.length; i++) { + if (serializedData[i]) { + formdata.append(serializedData[i][0], serializedData[i][1]); + } + } + } + + options.data = null; + + var s = $.extend(true, {}, $.ajaxSettings, options, { + contentType: false, + processData: false, + cache: false, + type: method || "POST", + }); + + if (options.uploadProgress) { + // workaround because jqXHR does not expose upload property + s.xhr = function () { + var xhr = $.ajaxSettings.xhr(); + if (xhr.upload) { + xhr.upload.addEventListener( + "progress", + function (event) { + var percent = 0; + var position = + event.loaded || + event.position; /*event.position is deprecated*/ + var total = event.total; + if (event.lengthComputable) { + percent = Math.ceil((position / total) * 100); + } + options.uploadProgress(event, position, total, percent); + }, + false + ); + } + return xhr; + }; + } + + s.data = null; + var beforeSend = s.beforeSend; + s.beforeSend = function (xhr, o) { + //Send FormData() provided by user + if (options.formData) { + o.data = options.formData; + } else { + o.data = formdata; + } + if (beforeSend) { + beforeSend.call(this, xhr, o); + } + }; + return $.ajax(s); + } + + // private function for handling file uploads (hat tip to YAHOO!) + function fileUploadIframe(a) { + var form = $form[0], + el, + i, + s, + g, + id, + $io, + io, + xhr, + sub, + n, + timedOut, + timeoutHandle; + var deferred = $.Deferred(); + + // #341 + deferred.abort = function (status) { + xhr.abort(status); + }; + + if (a) { + // ensure that every serialized input is still enabled + for (i = 0; i < elements.length; i++) { + el = $(elements[i]); + if (hasProp) { + el.prop("disabled", false); + } else { + el.removeAttr("disabled"); + } + } + } + + s = $.extend(true, {}, $.ajaxSettings, options); + s.context = s.context || s; + id = "jqFormIO" + new Date().getTime(); + if (s.iframeTarget) { + $io = $(s.iframeTarget); + n = $io.attr2("name"); + if (!n) { + $io.attr2("name", id); + } else { + id = n; + } + } else { + $io = $('