-
Notifications
You must be signed in to change notification settings - Fork 130
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
add checks for inline javascript #868
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,8 +26,11 @@ | |
import java.io.File; | ||
import java.io.IOException; | ||
import java.net.URL; | ||
import java.util.ArrayList; | ||
import java.util.Enumeration; | ||
import java.util.HashMap; | ||
import java.util.List; | ||
import java.util.Locale; | ||
import java.util.Map; | ||
import java.util.concurrent.Callable; | ||
import java.util.jar.JarEntry; | ||
|
@@ -36,7 +39,11 @@ | |
import junit.framework.TestResult; | ||
import junit.framework.TestSuite; | ||
import org.apache.commons.io.FileUtils; | ||
import org.dom4j.Attribute; | ||
import org.dom4j.Document; | ||
import org.dom4j.Element; | ||
import org.dom4j.Namespace; | ||
import org.dom4j.Node; | ||
import org.dom4j.ProcessingInstruction; | ||
import org.dom4j.io.SAXReader; | ||
import org.jvnet.hudson.test.junit.GroupedTest; | ||
|
@@ -88,6 +95,8 @@ | |
private final URL jelly; | ||
private final JellyClassLoaderTearOff jct; | ||
private final boolean requirePI; | ||
private List<String> errors = new ArrayList<>(); | ||
private boolean inlineJs = false; | ||
|
||
JellyCheck(URL jelly, String name, JellyClassLoaderTearOff jct, boolean requirePI) { | ||
super(name); | ||
|
@@ -103,11 +112,59 @@ | |
if (requirePI) { | ||
ProcessingInstruction pi = dom.processingInstruction("jelly"); | ||
if (pi == null || !pi.getText().contains("escape-by-default")) { | ||
throw new AssertionError("<?jelly escape-by-default='true'?> is missing in "+jelly); | ||
errors.add("<?jelly escape-by-default='true'?> is missing in "+jelly); | ||
} | ||
|
||
} | ||
// TODO: what else can we check statically? use of taglibs? | ||
checkScriptElement(dom); | ||
checkJavaScriptAttributes(dom); | ||
if (!errors.isEmpty()) { | ||
if (inlineJs) { | ||
errors.add("Please visit https://www.jenkins.io/doc/developer/security/csp/ for more details."); | ||
} | ||
String message = String.join("\n", errors); | ||
throw new AssertionError(message); | ||
} | ||
} | ||
|
||
private void checkJavaScriptAttributes(Document dom) { | ||
List<Node> allNodes = dom.selectNodes("//*"); | ||
allNodes.forEach(n -> { | ||
Element element = (Element) n; | ||
Attribute onclick = element.attribute("onclick"); | ||
Attribute checkUrl = element.attribute("checkUrl"); | ||
Attribute checkDependsOn = element.attribute("checkDependsOn"); | ||
if (checkUrl != null && checkDependsOn == null) { | ||
errors.add("Usage of 'checkUrl' without 'checkDependsOn' in "+jelly); | ||
inlineJs = true; | ||
} | ||
if (onclick != null && element.getNamespace() != Namespace.NO_NAMESPACE) { | ||
errors.add("Usage of 'onclick' from a taglib in "+jelly); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. could we link to a page explaining how to fix this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When used via a taglib it mean the resulting tag will also have that onclick handler. Afaik for all places in core that define an |
||
inlineJs = true; | ||
} | ||
List<Attribute> attributes = element.attributes(); | ||
if (element.getNamespace() == Namespace.NO_NAMESPACE && !attributes.isEmpty()) { | ||
attributes.forEach(a -> { | ||
if (a.getName().startsWith("on")) { | ||
errors.add("Usage of inline event handler '" + a.getName() + "' in "+jelly); | ||
inlineJs = true; | ||
} | ||
}); | ||
} | ||
}); | ||
} | ||
|
||
private void checkScriptElement(Document dom) { | ||
List<Node> scriptTags = dom.selectNodes("//script"); | ||
scriptTags.forEach( n -> { | ||
Element element = (Element) n; | ||
String typeAttribute = element.attributeValue("type"); | ||
if (element.attributeValue("src") == null && (typeAttribute == null || | ||
!"application/json".equals(typeAttribute.toLowerCase(Locale.US)))) { | ||
errors.add("inline <script> element in "+jelly); | ||
inlineJs = true; | ||
} | ||
}); | ||
} | ||
|
||
private boolean isConfigJelly() { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm wondering if this is clear enough to people, e.g. in most cases people need to add a
checkDependsOn=""
.Personally I think this isn't clear enough on its own.
(It would be good if this could be fixed so it doesn't need the empty checkDependsOn)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably when we have CSP enabled by default and all plugins are adjusted we could remove an empty
checkDependsOn
, until then we need it to find out if we have to run theeval
on thecheckUrl
.We could add a link to https://www.jenkins.io/doc/developer/security/csp/ whenever there is an inline javascript found.