An Add-on for OWASP ZAP to export alerts of a web application as Issues to JIRA

Kasun Balasooriya
6 min readDec 14, 2015

--

Hi all! As promised in this post I’m going to explain how I used the Zap-Extension project to develop an add-on for ZAP. As I mentioned in my previous post I used the sample provided to create a menu item. I used that to build what I needed.

The UI and usage walk-through

I was assigned with a task to create a plugin for ZAP so that a user can report alerts on generated by ZAP for web application as issues to JIRA. My initial plan was to use the report generator to create a new report file either in the XML or HTML format as a file and using that file to create the issues.

But instead I have used the existing session internally to create the issues. The following image shows the workflow of the add-on.

I have created a new menu Item under reports to create new Jira Issues.

public void hook(ExtensionHook extensionHook) { super.hook(extensionHook); if (getView() != null) { // Register our top menu item, as long as we're not running as a daemon // Use one of the other methods to add to a different menu list extensionHook.getHookMenu().addReportMenuItem(getMenuExample()); extensionHook.getHookView().addStatusPanel(getStatusPanel()); } }

The hook method takes care of “hooking” a new menu item. In this instance I have called addReportMenuItem to create a new menu item under reports.

Once the user clicks for the first time a window prompting for the JIRA home url , username and the password will appear.

The user has to do this only once and the credentials will be saved to a file to be used in the future. Once that is filled and done if the provided credentials and the url is valid then the user will be presented with the following screen.

The form uses the user credentials to call the jira rest api and get a list of projects that will be displayed in the combo box project key.

public void listJiraProjects() throws IOException, AuthenticationException { //list all the projects in comboBox cbProjectKeys Properties prop = new Properties(); InputStream input = new FileInputStream(Constant.getZapHome() + "/cred.properties"); prop.load(input); if (!(prop.getProperty("jiraUrl").equals("")) && !(prop.getProperty("jiraUsername").equals("")) && !(prop.getProperty("jiraPass").equals(""))) { String BASE_URL = prop.getProperty("jiraUrl"); String auth = new String(Base64.encode(prop.getProperty("jiraUsername") + ":" + prop.getProperty("jiraPass"))); String projects = JiraRestClient.invokeGetMethod(auth, BASE_URL + "/rest/api/2/project"); // rest call to get the list of projects JSONArray projectArray = new JSONArray(projects); for (int i = 0; i < projectArray.length(); i++) { JSONObject proj = projectArray.getJSONObject(i); cbProjectKeys.addItem(proj.getString("key") + " - " + proj.getString("name"));// add the projects to the combobox } AutoCompleteDecorator.decorate(cbProjectKeys); //enable auto-completion } else { throw (new AuthenticationException("Login Error !!")); } }

The user can assign a person for the issues and as in the previous instance the rest api is used to get a list of people who can be assigned for the issues.

private void getAssignees(String project) { //get a list of assignees for a project, list in combo box cbSelectAssignee Properties prop = new Properties(); InputStream input = null; try { input = new FileInputStream(Constant.getZapHome() + "/cred.properties"); prop.load(input); String BASE_URL = prop.getProperty("jiraUrl"); String auth = new String(Base64.encode(prop.getProperty("jiraUsername") + ":" + prop.getProperty("jiraPass"))); String asignees = JiraRestClient.invokeGetMethod(auth, BASE_URL + "/rest/api/2/user/assignable" + "/multiProjectSearch?projectKeys=" + project); JSONArray assigneeArray = new JSONArray(asignees); if (cbSelectAssignee.getSelectedItem() != null) { //to stop regenrating users cbSelectAssignee.removeAllItems(); } for (int i = 0; i < assigneeArray.length(); i++) { JSONObject user = assigneeArray.getJSONObject(i); cbSelectAssignee.addItem(user.getString("name")); } AutoCompleteDecorator.decorate(cbSelectAssignee); } catch (FileNotFoundException e) { log.error(e.getMessage(), e); } catch (IOException e) { log.error(e.getMessage(), e); } catch (AuthenticationException e) { log.error(e.getMessage(), e); } }

I have used a jql query to get all the open issues of a selected project in JIRA as mentioned in the workflow in the stages To Do, In Progress or Open.

private JSONObject getAllOpenIssues(String projectkey){ JiraRestClient jiraRest=new JiraRestClient(); String responseIssues; JSONObject allOpenIssues=null; try { String[] creds=this.loginUser(); String auth=creds[1]; String BASE_URL = creds[0]; responseIssues=jiraRest.invokeGetMethod(auth, BASE_URL + "/rest/api/2/search?jql=project="+projectkey+"%20AND%20" + "(status=%22Open%22OR%20status=%22In%20Progress%22%20OR%20status=%22To%20Do%22)" + "+order+by+id&fields=key,summary,description,status&maxResults=1000"); allOpenIssues=new JSONObject(responseIssues); } catch (IOException e) { log.error(e.getMessage(),e); } catch (AuthenticationException e) { log.error(e.getMessage(), e); } return allOpenIssues; }

Based on the resluts of above jql query I will check for existing jira issues in the current list and update and create new jiras as necessary.

public boolean checkForIssueExistence(String issue, String projectKey){ Boolean existance=false; JSONObject currentIssue=new JSONObject(issue); JSONObject allOpenIssues=this.getAllOpenIssues(projectKey); if(allOpenIssues.getJSONArray("issues").length()!=0) { JSONArray issueArray = allOpenIssues.getJSONArray("issues"); for (int i = 0; i < issueArray.length(); i++) { if (currentIssue.getJSONObject("fields").getString("summary").equals (issueArray.getJSONObject(i).getJSONObject("fields").getString("summary"))) { existance = true; updateIssueID = issueArray.getJSONObject(i).getString("id"); currentOpenIssue = issueArray.getJSONObject(i); break; } } } return existance; }public static String updateIssueID; public static JSONObject currentOpenIssue; public void updateExistingIssue(String issue,String auth, String BASE_URL,int currentIssueIndex) throws AuthenticationException { JSONObject currntIssue=new JSONObject(issue); JiraRestClient jira=new JiraRestClient(); String currentOpenIssueDescription=currentOpenIssue.getJSONObject("fields").getString("description"); String currentDescription=currntIssue.getJSONObject("fields").getString("description"); if(currentOpenIssueDescription.hashCode()!=currentDescription.hashCode()){ //if the descriptions are not the same String updatedDescription=StringEscapeUtils.escapeJava(this.updateIssueURLS(currentOpenIssueDescription, currentIssueIndex)); currentOpenIssue.getJSONObject("fields").put("description", updatedDescription); String editIssueData = "{\"fields\": {\"description\":\"" + updatedDescription + "\"}}"; jira.invokePutMethod(auth, BASE_URL + "/rest/api/2/issue/" + updateIssueID, editIssueData); } }

Based on the threat level a user can decide on what issues should be exported as issues to the JIRA.

Once the alert levels are selected the issues can be created in JIRA by clicking export.

After all the issues are exported to JIRA a confirmation dialog will pop up confirming the action.

private void btnExportActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnExportActionPerformed String project_key = cbProjectKeys.getSelectedItem().toString().substring(0, cbProjectKeys.getSelectedItem().toString().indexOf(" ")); String issueList[],creds[]; JiraRestClient jira = new JiraRestClient(); int issueCount; String issue; try { creds=this.loginUser(); String auth = creds[1]; String BASE_URL = creds[0]; if (cbProjectKeys.getSelectedItem().toString() != null && cbSelectAssignee.getSelectedItem().toString() != null) { XmlDomParser xmlParser = new XmlDomParser(); if(cbHigh.isSelected()|| cbMedium.isSelected()|| cbLow.isSelected()) { issueList = xmlParser.parseXmlDoc(project_key, cbSelectAssignee.getSelectedItem().toString(), cbHigh.isSelected(), cbMedium.isSelected(), cbLow.isSelected()); // parse xml report with filters issueCount = issueList.length; if (issueCount != 0) { //proceed if the issue count is > 1 for (int i = 0; i < issueCount; i++) { //create Issues in jira if(xmlParser.checkForIssueExistence(issueList[i],project_key)){ //update if the issue already exists xmlParser.updateExistingIssue(issueList[i],auth,BASE_URL,i); }else { //create a new issue if not issue = jira.invokePostMethod(auth, BASE_URL + "/rest/api/2/issue", issueList[i]); } } this.dispose(); View.getSingleton().showMessageDialog("Done creating issues!!"); } else { //abort if the issue count is = 0 this.dispose(); View.getSingleton().showMessageDialog("No alerts found !!"); } }else{ View.getSingleton().showMessageDialog("Select alert levels to create issues !!"); } } } catch (AuthenticationException e) { //authentication faliure log.error(e.getMessage(), e); View.getSingleton().showWarningDialog("Authentication failed Check credentials "); this.dispose(); } catch (FileNotFoundException e) { //credential file not found ; show the credential form to recreate log.error(e.getMessage(), e); View.getSingleton().showWarningDialog("Credential file not found !!"); CredentialForm credForm = new CredentialForm(); credForm.show(); this.dispose(); } catch (IOException e) { //failed to read file log.error(e.getMessage(), e); this.dispose(); } }//GEN-LAST:event_btnExportActionPerformed

Exceptions and error logging

Exceptions can be used by Extending the exception class.

Error logging can be done using

catch (AuthenticationException e) { //jira throws a capcha user has to log and try again log.error(e.getMessage(), e);

The errors will be logged in the zap.log file found in the .ZAP directory.

Here is a sample of a jira issue created by the extension.

Closing Remarks:

I will add details on how to add methods to extend this functionality in to the zap-api and further developments to the extension in my next blogpsot.

My git fork can be found at : https://github.com/0xkasun/zap-extensions/tree/alpha

KIT! 🙂

Originally published at http://neatrick.wordpress.com on December 14, 2015.

--

--

Kasun Balasooriya
Kasun Balasooriya

No responses yet