Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 63 additions & 42 deletions src/main/java/io/shiftleft/controller/AdminController.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,25 @@ public class AdminController {

// helper
private boolean isAdmin(String auth)
{
// Secure JWT key - in production, this should be stored securely, not hardcoded
private static final Key JWT_SECRET_KEY = Keys.secretKeyFor(SignatureAlgorithm.HS256);

private boolean isAdmin(String authToken) {
try {
ByteArrayInputStream bis = new ByteArrayInputStream(Base64.getDecoder().decode(auth));
ObjectInputStream objectInputStream = new ObjectInputStream(bis);
Object authToken = objectInputStream.readObject();
return ((AuthToken) authToken).isAdmin();
// Verify and parse the JWT token
Claims claims = Jwts.parserBuilder()
.setSigningKey(JWT_SECRET_KEY)
.build()
.parseClaimsJws(authToken)
.getBody();

// Check if the role is ADMIN
return "ADMIN".equals(claims.get("role", String.class));
} catch (Exception ex) {
System.out.println(" cookie cannot be deserialized: "+ex.getMessage());
return false;
System.out.println("Invalid authentication token: " + ex.getMessage());
return false;
}
}
}

//
@RequestMapping(value = "/admin/printSecrets", method = RequestMethod.POST)
Expand Down Expand Up @@ -81,47 +89,60 @@ public String doGetPrintSecrets(@CookieValue(value = "auth", defaultValue = "not
* @return redirect to company numbers
* @throws Exception
*/
@RequestMapping(value = "/admin/login", method = RequestMethod.POST)
public String doPostLogin(@CookieValue(value = "auth", defaultValue = "notset") String auth, @RequestBody String password, HttpServletResponse response, HttpServletRequest request) throws Exception {
@RequestMapping(value = "/admin/login", method = RequestMethod.POST)
public String doPostLogin(@CookieValue(value = "auth", defaultValue = "notset") String auth,
@RequestBody String password,
HttpServletResponse response,
HttpServletRequest request) throws Exception {
String succ = "redirect:/admin/printSecrets";
String fail = "redirect:/admin/login";

try {
// no cookie no fun
if (!auth.equals("notset")) {
if(isAdmin(auth)) {
request.getSession().setAttribute("auth",auth);
return succ;
// Check if user is already authenticated via cookie
if (!auth.equals("notset")) {
if (isAdmin(auth)) {
request.getSession().setAttribute("auth", auth);
return succ;
}
}
}

// split password=value
String[] pass = password.split("=");
if(pass.length!=2) {
// Parse the password from request body
String[] pass = password.split("=");
if (pass.length != 2) {
return fail;
}

// Verify password (consider using a more secure password handling mechanism)
if (pass[1] != null && pass[1].length() > 0 && pass[1].equals("shiftleftsecret")) {
// Generate JWT token instead of serialized object
String jwtToken = Jwts.builder()
.setSubject("admin")
.claim("role", "ADMIN")
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + 3600000)) // 1 hour expiration
.signWith(JWT_SECRET_KEY)
.compact();

// Set the JWT as a cookie
Cookie authCookie = new Cookie("auth", jwtToken);
authCookie.setHttpOnly(true); // Prevent JavaScript access
authCookie.setSecure(true); // Send only over HTTPS
authCookie.setPath("/"); // Available on all paths
authCookie.setMaxAge(3600); // 1 hour expiration
response.addCookie(authCookie);

// Store in session to maintain authentication after redirect
request.getSession().setAttribute("auth", jwtToken);

return succ;
}
return fail;
} catch (Exception ex) {
ex.printStackTrace();
return fail;
}
// compare pass
if(pass[1] != null && pass[1].length()>0 && pass[1].equals("shiftleftsecret"))
{
AuthToken authToken = new AuthToken(AuthToken.ADMIN);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(authToken);
String cookieValue = new String(Base64.getEncoder().encode(bos.toByteArray()));
response.addCookie(new Cookie("auth", cookieValue ));

// cookie is lost after redirection
request.getSession().setAttribute("auth",cookieValue);

return succ;
}
return fail;
}
catch (Exception ex)
{
ex.printStackTrace();
// no succ == fail
return fail;
}
}

}

/**
Expand Down
164 changes: 107 additions & 57 deletions src/main/java/io/shiftleft/controller/CustomerController.java
Original file line number Diff line number Diff line change
Expand Up @@ -109,32 +109,36 @@ private void dispatchEventToSalesForce(String event)
* @param customerId
* @return retrieved customer
*/
@RequestMapping(value = "/customers/{customerId}", method = RequestMethod.GET)
public Customer getCustomer(@PathVariable("customerId") Long customerId) {
@RequestMapping(value = "/customers/{customerId}", method = RequestMethod.GET)
public Customer getCustomer(@PathVariable("customerId") Long customerId) {

/* validate customer Id parameter */
if (null == customerId) {
/* validate customer Id parameter */
if (null == customerId) {
throw new InvalidCustomerRequestException();
}

Customer customer = customerRepository.findOne(customerId);
if (null == customer) {
throw new CustomerNotFoundException();
}
}

Account account = new Account(4242l,1234, "savings", 1, 0);
log.info("Account Data is {}", account);
log.info("Customer Data is {}", customer);
Customer customer = customerRepository.findOne(customerId);
if (null == customer) {
throw new CustomerNotFoundException();
}

try {
dispatchEventToSalesForce(String.format(" Customer %s Logged into SalesForce", customer));
} catch (Exception e) {
log.error("Failed to Dispatch Event to SalesForce . Details {} ", e.getLocalizedMessage());
Account account = new Account(4242l, 1234, "savings", 1, 0);

// Log only non-sensitive information like IDs instead of entire objects
log.info("Processing account ID: null", account.getId());
log.info("Retrieved customer ID: null", customer.getId());

try {
// Avoid logging sensitive customer information in the SalesForce event
dispatchEventToSalesForce(String.format("Customer ID %s logged into SalesForce", customer.getId()));
} catch (Exception e) {
// Don't include exception details in logs that might contain sensitive data
log.error("Failed to dispatch event to SalesForce. Error type: null", e.getClass().getName());
}

}
return customer;
}

return customer;
}

/**
* Handler for / loads the index.tpl
Expand Down Expand Up @@ -216,7 +220,7 @@ public void loadSettings(HttpServletResponse httpResponse, WebRequest request) t
* @param request
* @throws Exception
*/
@RequestMapping(value = "/saveSettings", method = RequestMethod.GET)
@RequestMapping(value = "/saveSettings", method = RequestMethod.GET)
public void saveSettings(HttpServletResponse httpResponse, WebRequest request) throws Exception {
// "Settings" will be stored in a cookie
// schema: base64(filename,value1,value2...), md5sum(base64(filename,value1,value2...))
Expand All @@ -228,8 +232,8 @@ public void saveSettings(HttpServletResponse httpResponse, WebRequest request) t

String settingsCookie = request.getHeader("Cookie");
String[] cookie = settingsCookie.split(",");
if(cookie.length<2) {
httpResponse.getOutputStream().println("Malformed cookie");
if(cookie.length<2) {
httpResponse.getOutputStream().println("Malformed cookie");
throw new Exception("cookie is incorrect");
}

Expand All @@ -238,63 +242,104 @@ public void saveSettings(HttpServletResponse httpResponse, WebRequest request) t
// Check md5sum
String cookieMD5sum = cookie[1];
String calcMD5Sum = DigestUtils.md5Hex(base64txt);
if(!cookieMD5sum.equals(calcMD5Sum))
if(!cookieMD5sum.equals(calcMD5Sum))
{
httpResponse.getOutputStream().println("Wrong md5");
throw new Exception("Invalid MD5");
}

// Now we can store on filesystem
String[] settings = new String(Base64.getDecoder().decode(base64txt)).split(",");
// storage will have ClassPathResource as basepath
if (settings.length == 0 || settings[0] == null || settings[0].isEmpty()) {
httpResponse.getOutputStream().println("Invalid filename");
throw new Exception("Invalid filename");
}

// Sanitize filename and prevent directory traversal
String fileName = FilenameUtils.getName(settings[0]);

// Extra validation to ensure no path traversal
if (!fileName.equals(settings[0]) || fileName.contains("..")) {
httpResponse.getOutputStream().println("Invalid file path");
throw new Exception("Directory traversal attempt detected");
}

// storage will have ClassPathResource as basepath
ClassPathResource cpr = new ClassPathResource("./static/");
File file = new File(cpr.getPath()+settings[0]);
if(!file.exists()) {
file.getParentFile().mkdirs();

// Construct safe file path within base directory
Path basePath = Paths.get(cpr.getPath()).normalize();
Path resolvedPath = basePath.resolve(fileName).normalize();

// Validate that the final path is still within the intended base directory
if (!resolvedPath.startsWith(basePath)) {
httpResponse.getOutputStream().println("Invalid file path");
throw new Exception("Path traversal detected");
}

File file = resolvedPath.toFile();
@RequestMapping(value = "/debug", method = RequestMethod.GET, produces = MediaType.TEXT_PLAIN_VALUE)
public String debug(@RequestParam String customerId,
@RequestParam int clientId,
@RequestParam String firstName,
@RequestParam String lastName,
@RequestParam String dateOfBirth,
@RequestParam String ssn,
@RequestParam String socialSecurityNum,
@RequestParam String tin,
@RequestParam String phoneNumber,
HttpServletResponse httpResponse,
WebRequest request) throws IOException {

FileOutputStream fos = new FileOutputStream(file, true);
// First entry is the filename -> remove it
String[] settingsArr = Arrays.copyOfRange(settings, 1, settings.length);
// on setting at a linez
fos.write(String.join("\n",settingsArr).getBytes());
fos.write(("\n"+cookie[cookie.length-1]).getBytes());
fos.close();
httpResponse.getOutputStream().println("Settings Saved");
}
// empty for now, because we debug
Set<Account> accounts1 = new HashSet<Account>();
//dateofbirth example -> "1982-01-10"
Customer customer1 = new Customer(customerId, clientId, firstName, lastName, DateTime.parse(dateOfBirth).toDate(),
ssn, socialSecurityNum, tin, phoneNumber, new Address("Debug str",
"", "Debug city", "CA", "12345"),
accounts1);

customerRepository.save(customer1);
httpResponse.setStatus(HttpStatus.CREATED.value());
httpResponse.setHeader("Location", String.format("%s/customers/%s",
request.getContextPath(), customer1.getId()));

// Properly encode the customer information to prevent XSS
return Encode.forHtml(customer1.toString());
}

/**
* Debug test for saving and reading a customer
*
* @param firstName String
* @param lastName String
* @param dateOfBirth String
* @param ssn String
* @param tin String
* @param phoneNumber String
* @param httpResponse
* @param request
* @return String
* @throws IOException
*/
@RequestMapping(value = "/debug", method = RequestMethod.GET)
@RequestMapping(value = "/debug", method = RequestMethod.GET)
public String debug(@RequestParam String customerId,
@RequestParam int clientId,
@RequestParam String firstName,
@RequestParam int clientId,
@RequestParam String firstName,
@RequestParam String lastName,
@RequestParam String dateOfBirth,
@RequestParam String ssn,
@RequestParam String socialSecurityNum,
@RequestParam String socialSecurityNum,
@RequestParam String tin,
@RequestParam String phoneNumber,
HttpServletResponse httpResponse,
WebRequest request) throws IOException{
WebRequest request) throws IOException{

// empty for now, because we debug
Set<Account> accounts1 = new HashSet<Account>();
//dateofbirth example -> "1982-01-10"
Customer customer1 = new Customer(customerId, clientId, firstName, lastName, DateTime.parse(dateOfBirth).toDate(),
ssn, socialSecurityNum, tin, phoneNumber, new Address("Debug str",

// Sanitize all user inputs before creating the customer object
String sanitizedCustomerId = HtmlUtils.htmlEscape(customerId);
String sanitizedFirstName = HtmlUtils.htmlEscape(firstName);
String sanitizedLastName = HtmlUtils.htmlEscape(lastName);
String sanitizedSSN = HtmlUtils.htmlEscape(ssn);
String sanitizedSocialSecurityNum = HtmlUtils.htmlEscape(socialSecurityNum);
String sanitizedTIN = HtmlUtils.htmlEscape(tin);
String sanitizedPhoneNumber = HtmlUtils.htmlEscape(phoneNumber);

Customer customer1 = new Customer(sanitizedCustomerId, clientId, sanitizedFirstName,
sanitizedLastName, DateTime.parse(dateOfBirth).toDate(),
sanitizedSSN, sanitizedSocialSecurityNum, sanitizedTIN,
sanitizedPhoneNumber, new Address("Debug str",
"", "Debug city", "CA", "12345"),
accounts1);

Expand All @@ -303,9 +348,14 @@ ssn, socialSecurityNum, tin, phoneNumber, new Address("Debug str",
httpResponse.setHeader("Location", String.format("%s/customers/%s",
request.getContextPath(), customer1.getId()));

return customer1.toString().toLowerCase().replace("script","");
// Properly escape the output before returning
return HtmlUtils.htmlEscape(customer1.toString());
}





/**
* Debug test for saving and reading a customer
*
Expand Down
Loading
Loading