Skip to content

Commit

Permalink
Merge pull request #2984 from SCADA-LTS/feature/#2983_Prevent_XSS_for…
Browse files Browse the repository at this point in the history
…_URLs

#2983 Prevent XSS for URLs
  • Loading branch information
Limraj authored Aug 29, 2024
2 parents 98cc163 + 2bd5adb commit c07ab81
Show file tree
Hide file tree
Showing 16 changed files with 323 additions and 172 deletions.
20 changes: 10 additions & 10 deletions WebContent/WEB-INF/jsp/pointHierarchySLTS.jsp
Original file line number Diff line number Diff line change
Expand Up @@ -601,7 +601,7 @@ var messages = {
$.ajax({
type: "GET",
dataType: "json",
url:myLocation+"/pointHierarchy/paths/"+key+"/"+folder,
url:myLocation+"pointHierarchy/paths/"+key+"/"+folder,
success: function(msg){
var path="/";
if (msg.length>0) {
Expand Down Expand Up @@ -753,7 +753,7 @@ var messages = {
$.ajax({
type: "POST",
dataType: "json",
url:myLocation+'/pointHierarchy/move/'+toMove.key+'/'+toMove.oldParentId+'/'+toMove.newParentId+'/'+nodeDragAndDrop.isFolder(),
url:myLocation+'pointHierarchy/move/'+toMove.key+'/'+toMove.oldParentId+'/'+toMove.newParentId+'/'+nodeDragAndDrop.isFolder(),
success: function(msg){
$button.hide();
$button.stopSpin();
Expand Down Expand Up @@ -786,7 +786,7 @@ var messages = {
glyph: glyph_opts,
selectMode: 2,
source: {
url: myLocation+"/pointHierarchy/0",
url: myLocation+"pointHierarchy/0",
debugDelay: 0,
cache: false},
toggleEffect: { effect: "drop", options: {direction: "left"}, duration: 100 },
Expand All @@ -802,7 +802,7 @@ var messages = {
},
lazyLoad: function(event, data) {
data.result = {
url: myLocation+"/pointHierarchy/"+data.node.key,
url: myLocation+"pointHierarchy/"+data.node.key,
cache: false,
debugDelay: 0
};
Expand Down Expand Up @@ -832,7 +832,7 @@ var messages = {
$.ajax({
type: "POST",
dataType: "json",
url:myLocation+"/pointHierarchy/new/0/"+dialog.getModalBody().find('input').val(),
url:myLocation+"pointHierarchy/new/0/"+dialog.getModalBody().find('input').val(),
success: function(msg){
var titleNewNode = dialog.getModalBody().find('input').val();
dialog.getModalBody().html('<div><h3>'+messages.folder+':</h3><ul><li>'+messages.key+':<b>'+msg+'</b></li><li>'+messages.title+':<b>'+titleNewNode+'</b></li></ul></div>');
Expand Down Expand Up @@ -899,7 +899,7 @@ var messages = {
$.ajax({
type: "POST",
dataType: "json",
url:myLocation+"/pointHierarchy/del/"+getParentId(nodeActivate)+"/"+nodeActivate.key+"/"+nodeActivate.isFolder(),
url:myLocation+"pointHierarchy/del/"+getParentId(nodeActivate)+"/"+nodeActivate.key+"/"+nodeActivate.isFolder(),
success: function(msg){
dialog.getModalBody().html('<div><h3>'+messages.folderRemoved+':</h3><ul><li>'+messages.key+':<b>'+nodeActivate.key+'</b></li><li>'+messages.title+':<b>'+nodeActivate.title+'</b></li><li>'+messages.msg+':'+msg+'</li></ul></div>');
$button.hide();
Expand Down Expand Up @@ -949,7 +949,7 @@ var messages = {
$.ajax({
type: "POST",
dataType: "json",
url:myLocation+"/pointHierarchy/del/"+getParentId(nodeActivate)+"/"+nodeActivate.key+"/"+nodeActivate.isFolder(),
url:myLocation+"pointHierarchy/del/"+getParentId(nodeActivate)+"/"+nodeActivate.key+"/"+nodeActivate.isFolder(),
success: function(msg){
dialog.getModalBody().html('<div><h3>'+messages.movedElement+':</h3><ul><li>'+messages.key+':<b>'+nodeActivate.key+'</b></li><li>'+messages.title+':<b>'+nodeActivate.title+'</b></li><li>'+messages.msg+':'+msg+'</li></ul></div>');
$button.hide();
Expand Down Expand Up @@ -1032,7 +1032,7 @@ var messages = {
$.ajax({
type: "POST",
dataType: "json",
url:myLocation+"/pointHierarchy/edit/"+getParentId(nodeActivate)+"/"+nodeActivate.key+"/"+newTitle,
url:myLocation+"pointHierarchy/edit/"+getParentId(nodeActivate)+"/"+nodeActivate.key+"/"+newTitle,
success: function(msg){
var titleNewNode = dialog.getModalBody().find('input').val();
dialog.getModalBody().html('<div><h3>'+messages.folderChange+':</h3><ul><li>'+messages.key+':<b>'+nodeActivate.key+'</b></li><li>'+messages.oldTitle+':<b>'+nodeActivate.title+'</b></li><li>'+messages.newTitle+':<b>'+newTitle+'</b></li></ul></div>');
Expand Down Expand Up @@ -1140,7 +1140,7 @@ var messages = {
$.ajax({
type: "POST",
dataType: "json",
url:myLocation+"/viewutil/"+locale,
url:myLocation+"viewutil/"+locale,
success: function(msg){
location.reload();
},
Expand Down Expand Up @@ -1227,7 +1227,7 @@ var messages = {
$.ajax({
type: "GET",
dataType: "json",
url:myLocation+"/pointHierarchy/find/"+queryGlobal+"/"+page,
url:myLocation+"pointHierarchy/find/"+queryGlobal+"/"+page,
success: function(msg){
if(msg !== undefined ) {
Expand Down
2 changes: 1 addition & 1 deletion WebContent/WEB-INF/jsp/scripting.jsp
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
if (!myLocation) {
myLocation = location.protocol + "//" + location.host + "/" + appScada + "/";
}
var urlGetDataPoints = "/api/datapoint/getAll";
var urlGetDataPoints = "api/datapoint/getAll";
function executeScript(){
var xid = jQuery("#xid");
// saveScript() nie zdarzy zapisac !!!
Expand Down
20 changes: 18 additions & 2 deletions WebContent/WEB-INF/spring-security.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
<!-- Other -->
<intercept-url pattern="/**" access="hasRole('ROLE_SERVICES')" requires-channel="https"/>

<custom-filter ref="xssFilter" before="FIRST"/>
<custom-filter position="BASIC_AUTH_FILTER" ref="basicAuthFilter"/>
<session-management session-fixation-protection="none" />
</http>
Expand All @@ -46,6 +47,7 @@
<!-- Other -->
<intercept-url pattern="/**" access="hasAnyRole('ROLE_HTTPDS')" />

<custom-filter ref="xssFilter" before="FIRST"/>
<custom-filter position="BASIC_AUTH_FILTER" ref="basicAuthFilter"/>
<session-management session-fixation-protection="none" />
</http>
Expand All @@ -68,6 +70,7 @@
<!-- Other -->
<intercept-url pattern="/**" access="hasAnyRole('ROLE_ADMIN', 'ROLE_USER', 'ROLE_HTTPDS')" requires-channel="https"/>

<custom-filter ref="xssFilter" before="FIRST"/>
<custom-filter position="BASIC_AUTH_FILTER" ref="basicAuthFilter"/>
<session-management session-fixation-protection="none"/>
</http>
Expand All @@ -90,6 +93,7 @@
<!-- Other -->
<intercept-url pattern="/**" access="hasAnyRole('ROLE_ADMIN')" requires-channel="https"/>

<custom-filter ref="xssFilter" before="FIRST"/>
<custom-filter position="BASIC_AUTH_FILTER" ref="basicAuthFilter"/>
<session-management session-fixation-protection="none"/>
</http>
Expand All @@ -112,6 +116,7 @@
<!-- Other -->
<intercept-url pattern="/**" access="hasAnyRole('ROLE_ADMIN')" requires-channel="https"/>

<custom-filter ref="xssFilter" before="FIRST"/>
<custom-filter position="BASIC_AUTH_FILTER" ref="basicAuthFilter"/>
<session-management session-fixation-protection="none"/>
</http>
Expand Down Expand Up @@ -440,13 +445,14 @@
authentication-failure-url="/login.htm?error" login-page="/login.htm"
password-parameter="password" username-parameter="username"
login-processing-url="/login.htm"/>

<custom-filter ref="xssFilter" before="FIRST"/>
<custom-filter after="SECURITY_CONTEXT_FILTER" ref="setDataSessionFilter" />
<session-management session-fixation-protection="none" />
<logout logout-url="/logout.htm" invalidate-session="true" delete-cookies="JSESSIONID"
success-handler-ref="headerWriterLogoutHandler"/>
</http>
<http-firewall ref="defaultHttpFirewall"/>
<http-firewall ref="strictHttpFirewall"/>
<!--http-firewall ref="defaultHttpFirewall"/-->

<!-- Websocket -->
<!--websocket-message-broker same-origin-disabled="true">
Expand Down Expand Up @@ -518,4 +524,14 @@
</b:bean>

<b:bean id="getSecurityBeans" class="org.scada_lts.web.beans.GetSecurityBeans"/>

<b:bean id="strictHttpFirewall" class="org.springframework.security.web.firewall.StrictHttpFirewall">
<b:property name="allowSemicolon" value="false"/>
<b:property name="allowUrlEncodedSlash" value="false"/>
<b:property name="allowUrlEncodedPercent" value="false"/>
<b:property name="allowBackSlash" value="false"/>
<b:property name="allowUrlEncodedPeriod" value="false"/>
</b:bean>

<b:bean id="xssFilter" class="org.scada_lts.web.security.XssFilter"/>
</b:beans>
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ test {
includeTestsMatching "com.serotonin.mango.util.AddLimitIfWithoutSqlDataSourceUtilsTest"
includeTestsMatching "com.serotonin.mango.util.StartStopDataPointsUtilsTestsSuite"
includeTestsMatching "org.scada_lts.utils.BlockingQueuesUtilsTest"
includeTestsMatching "org.scada_lts.web.security.XssUtilsTest"
}

testLogging {
Expand Down
7 changes: 5 additions & 2 deletions scadalts-ui/src/store/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export default new Vuex.Store({
// useCredentials: true,
// credentials: 'same-origin',
},
webSocketUrl: 'ws-scada/alarmLevel',
webSocketUrl: 'ws-scada',

timePeriods: [
{ id: 1, label: i18n.t('common.timeperiod.seconds') },
Expand All @@ -98,7 +98,10 @@ export default new Vuex.Store({
},
mutations: {
updateWebSocketUrl(state) {
state.webSocketUrl = getAppLocation() + state.webSocketUrl;
let base = getAppLocation();
if(!state.webSocketUrl.includes(base)) {
state.webSocketUrl = base + state.webSocketUrl;
}
},

updateRequestTimeout(state, timeout) {
Expand Down
2 changes: 1 addition & 1 deletion scadalts-ui/src/store/pointHierarchy/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ const gv = {
return new Promise((resolve, reject) => {
axios
.post(
`.//pointHierarchy/move/${nodeId}/${parentNodeId}/${newParentNodeId}/${isFolder}`,
`./pointHierarchy/move/${nodeId}/${parentNodeId}/${newParentNodeId}/${isFolder}`,
requestConfiguration,
)
.then((resp) => {
Expand Down
6 changes: 5 additions & 1 deletion scadalts-ui/src/store/websocketStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@ const webSocketModule = {

mutations: {
INIT_WEBSOCKET(state) {
let socket = new SockJS(getAppLocation() + state.webSocketUrl);
let base = getAppLocation();
if(!state.webSocketUrl.includes(base)) {
state.webSocketUrl = base + state.webSocketUrl;
}
let socket = new SockJS(state.webSocketUrl);
let client = Stomp.over(socket);
if(!state.debugMode) {
client.debug = () => {};
Expand Down
43 changes: 43 additions & 0 deletions src/org/scada_lts/utils/SystemSettingsUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ private SystemSettingsUtils() {}

public static final String EVENT_ASSIGN_ENABLED_KEY = "event.assign.enabled";

private static final String SECURITY_HTTP_QUERY_ACCESS_DENIED_REGEX_KEY = "scadalts.security.http.query.access.denied.regex";
private static final String SECURITY_HTTP_QUERY_ACCESS_GRANTED_REGEX_KEY = "scadalts.security.http.query.access.granted.regex";
private static final String SECURITY_HTTP_QUERY_LIMIT_KEY = "scadalts.security.http.query.limit";
private static final String SECURITY_HTTP_QUERY_PROTECT_ENABLED_KEY = "scadalts.security.http.query.protect.enabled";

private static final org.apache.commons.logging.Log LOG = LogFactory.getLog(SystemSettingsUtils.class);

public static DataPointSyncMode getDataPointSynchronizedMode() {
Expand Down Expand Up @@ -538,4 +543,42 @@ public static boolean isEventAssignEnabled() {
return false;
}
}

public static String getSecurityHttpQueryAccessDeniedRegex() {
try {
return ScadaConfig.getInstance().getConf().getProperty(SECURITY_HTTP_QUERY_ACCESS_DENIED_REGEX_KEY, "");
} catch (Exception e) {
LOG.error(e.getMessage());
return "";
}
}

public static String getSecurityHttpQueryAccessGrantedRegex() {
try {
return ScadaConfig.getInstance().getConf().getProperty(SECURITY_HTTP_QUERY_ACCESS_GRANTED_REGEX_KEY, "");
} catch (Exception e) {
LOG.error(e.getMessage());
return "";
}
}

public static int getSecurityHttpQueryLimit() {
try {
String securityHttpQueryXssLimit = ScadaConfig.getInstance().getConf().getProperty(SECURITY_HTTP_QUERY_LIMIT_KEY, "4001");
return Integer.parseInt(securityHttpQueryXssLimit);
} catch (Exception e) {
LOG.error(e.getMessage());
return 4002;
}
}

public static boolean isSecurityHttpQueryProtectEnabled() {
try {
String securityHttpQueryXssEnabled = ScadaConfig.getInstance().getConf().getProperty(SECURITY_HTTP_QUERY_PROTECT_ENABLED_KEY, "false");
return Boolean.parseBoolean(securityHttpQueryXssEnabled);
} catch (Exception e) {
LOG.error(e.getMessage());
return false;
}
}
}
35 changes: 35 additions & 0 deletions src/org/scada_lts/web/security/XssFilter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package org.scada_lts.web.security;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class XssFilter extends OncePerRequestFilter {

private static final Logger LOG = LogManager.getLogger(XssFilter.class);
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {

String queryString = request.getQueryString();
if (queryString != null && !XssUtils.validateHttpQuery(queryString)) {
LOG.warn("Potential XSS detected in request. Request URI: {}, Query: {}",
request.getRequestURI(), queryString);

try {
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
"Potential XSS detected in the request Query: " + queryString);
} catch (IOException e) {
throw new RuntimeException(e);
}
return;
}

filterChain.doFilter(request, response);
}
}
43 changes: 43 additions & 0 deletions src/org/scada_lts/web/security/XssUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package org.scada_lts.web.security;

import org.scada_lts.serorepl.utils.StringUtils;
import org.scada_lts.utils.SystemSettingsUtils;

import java.util.regex.Pattern;

public final class XssUtils {

private XssUtils() {}

private static final Pattern SECURITY_HTTP_ACCESS_DENIED_QUERY_REGEX = init(SystemSettingsUtils.getSecurityHttpQueryAccessDeniedRegex());
private static final Pattern SECURITY_HTTP_ACCESS_GRANTED_QUERY_REGEX = init(SystemSettingsUtils.getSecurityHttpQueryAccessGrantedRegex());
public static final int SECURITY_HTTP_ACCESS_GRANTED_QUERY_LIMIT = SystemSettingsUtils.getSecurityHttpQueryLimit();
public static final boolean SECURITY_HTTP_QUERY_PROTECT_ENABLED = SystemSettingsUtils.isSecurityHttpQueryProtectEnabled();

public static boolean validateHttpQuery(String query) {

if(!SECURITY_HTTP_QUERY_PROTECT_ENABLED)
return true;

if (query == null || query.isEmpty()) {
return false;
}

if(query.length() > SECURITY_HTTP_ACCESS_GRANTED_QUERY_LIMIT) {
return false;
}

if(SECURITY_HTTP_ACCESS_DENIED_QUERY_REGEX != null && SECURITY_HTTP_ACCESS_DENIED_QUERY_REGEX.matcher(query).matches()) {
return false;
}

return SECURITY_HTTP_ACCESS_GRANTED_QUERY_REGEX == null || SECURITY_HTTP_ACCESS_GRANTED_QUERY_REGEX.matcher(query).matches();
}

private static Pattern init(String regex) {
if(StringUtils.isEmpty(regex)) {
return null;
}
return Pattern.compile(regex);
}
}
Loading

0 comments on commit c07ab81

Please sign in to comment.