Name: Multiple SQL Injection vulnerabilities in Disk Pool Manager (DPM) Author: Adam Zabrocki () Date: November 27, 2009 (Yes, it's very old bug ;P) Description: LCG Disk Pool Manager (DPM) has been developed as part of the LCG project to provide a light-weight implementation of an SRM compliant Storage Element (SE). Since gLite 3.0 it is a standard gLite component, distributed and maintained as part of the gLite release. It has been developed at European Organization for Nuclear Research (CERN). DPM is a disk only SE, instead of a disk + MSS implementation like dCache or Castor. It may act as a replacement for the deprecated classic SE with the following advantages : - SRM interface (both v1.1 and v2.2) - Better scalability : DPM is allow to manage 100+ TB distributing the load over several servers - High performances - Light-weight management DPM is commonly used in most of the GRID projects including CERN WLCG, EGEE, ..., etc. Details: A multiple SQL Injection vulnerability has been found in Disk Pool Manager (DPM). Please read following details: "./srmv2.2/srmv2_xferreq.c" int ns1__srmGetRequestSummary (struct soap *soap, struct ns1__srmGetRequestSummaryRequest *req, struct ns1__srmGetRequestSummaryResponse_ *rep) { ... char *r_token; ... ... ... for (i = 0; i < nbtokens; i++) { ... r_token = req->arrayOfRequestTokens->stringArray[i]; if (strlen (r_token) > CA_MAXDPMTOKENLEN) { reptokenp->status->statusCode = SRM_USCOREINVALID_USCOREREQUEST; reptokenp->status->explanation = soap_strdup (soap, "Invalid request token"); nb_errors++; continue; } if (dpm_getonereqsummary (thip, r_token, &r_type, &r_status, &nbreqfiles, &nb_queued, &nb_progress, &nb_failed) < 0) { ... ... } ... ... } ... ... } This function is responsible fo reading and parsing requests. If length of "r_token" variable is not greather than CA_MAXDPMTOKENLEN function dpm_getonereqsummary() is called: "dpm/dpm_procsubr.c" dpm_getonereqsummary (thip, r_token, r_type, r_status, nbreqfiles, nb_queued, nb_progress, nb_failed) struct dpm_srv_thread_info *thip; char *r_token; char *r_type; int *r_status; int *nbreqfiles; int *nb_queued; int *nb_progress; int *nb_failed; { ... ... if (dpm_get_pending_req_by_token (&thip->dbfd, r_token, &dpm_req, 0, NULL) < 0 && dpm_get_req_by_token (&thip->dbfd, r_token, &dpm_req, 0, NULL) < 0) return (-1); ... ... } ... and: "dpm/dpm_mysql_ifce.c" dpm_get_pending_req_by_token(dbfd, r_token, dpm_req, lock, rec_addr) struct dpm_dbfd *dbfd; char *r_token; struct dpm_req *dpm_req; int lock; dpm_dbrec_addr *rec_addr; { char func[29]; static char query[] = "SELECT \ R_ORDINAL, R_TOKEN, R_UID, \ R_GID, CLIENT_DN, CLIENTHOST, \ R_TYPE, U_TOKEN, \ FLAGS, RETRYTIME, NBREQFILES, \ CTIME, STIME, ETIME, \ STATUS, ERRSTRING, GROUPS \ FROM dpm_pending_req \ WHERE r_token = '%s'"; static char query4upd[] = "SELECT ROWID, \ R_ORDINAL, R_TOKEN, R_UID, \ R_GID, CLIENT_DN, CLIENTHOST, \ R_TYPE, U_TOKEN, \ FLAGS, RETRYTIME, NBREQFILES, \ CTIME, STIME, ETIME, \ STATUS, ERRSTRING, GROUPS \ FROM dpm_pending_req \ WHERE r_token = '%s' \ FOR UPDATE"; MYSQL_RES *res; MYSQL_ROW row; char sql_stmt[1024]; MYSQL_RES *res; MYSQL_ROW row; char sql_stmt[1024]; strcpy (func, "dpm_get_pending_req_by_token"); sprintf (sql_stmt, lock ? query4upd : query, r_token); if (dpm_exec_query (func, dbfd, sql_stmt, &res)) return (-1); ... ... } This function creates a query to the MySQL Database - DPM supports three different databases: MySQL, PostgreSQL and Oracle. In this advisory I'm focused on MySQL database. This vulnerability may be in all supported databases. I haven't analyzed all the code. Variable 'r_token' isn't verified at all so SQL Injection attack is possible. Anyone with a certificate from a recognised CA can access the SRM interface so it is a serious vulnerability. SQL Injection exists not only in dpm_get_pending_req_by_token() function. Please read following list of functions which don't check inputs too: Function dpm_get_cpr_by_fullid() doesn't check 'r_token' Function dpm_get_cpr_by_surl() doesn't check 'r_token', 'surl' Function dpm_get_cpr_by_surls() doesn't check 'r_token', 'to_surl' Function dpm_get_gfr_by_fullid() doesn't check 'r_token' Function dpm_get_gfr_by_surl() doesn't check 'r_token' Function dpm_get_pending_req_by_token() doesn't check 'r_token' Function dpm_get_pending_reqs_by_u_desc() doesn't check 'u_token' Function dpm_get_pfr_by_fullid() doesn't check 'r_token' Function dpm_get_pfr_by_surl() doesn't check 'r_token' Function dpm_get_pool_entry() doesn't check 'poolname' variable but admin required so it isn't important. Function dpm_get_req_by_token() doesn't check 'r_token' Function dpm_get_reqs_by_u_desc() doesn't check 'u_token' Function dpm_get_spcmd_by_token() doesn't check 's_token' Function dpm_get_spcmd_by_u_desc() doesn't check 'u_token' Function dpm_insert_cpr_entry() doesn't check variable 's_token', 'r_token'. Function dpm_insert_fs_entry() doesn't check 'poolname' variable but admin required so it isn't important. Function dpm_insert_gfr_entry() doesn't check variable 's_token', 'r_token'. Function dpm_insert_pending_entry() doesn't check variable 'u_token', 'r_token'. Function dpm_insert_pfr_entry() doesn't check variable 's_token', 'r_token'. Function dpm_insert_pool_entry() doesn't check 'poolname' variable but admin required so it isn't important. Function dpm_insert_spcmd_entry() doesn't check variable 's_token', 'u_token' and (not important) 'poolname' - admin required. Functino dpm_insert_xferreq_entry() doesn't check variable 'u_token', 'r_token'. Function dpm_list_cpr_entry() doesn't check 'r_token' Functino dpm_list_fs_entry() doesn't check 'poolname' variable but admin required so it isn't important. Function dpm_list_gfr_entry() doesn't check 'r_token' Function dpm_list_pfr_entry() doesn't check 'r_token' Function dpm_update_cpr_entry() doesn't check 's_token' Function dpm_update_gfr_entry() doesn't check 's_token' Function dpm_update_pfr_entry() doesn't check 's_token' Function dpm_update_spcmd_entry() doesn't check 'poolname' variable but admin required so it isn't important. Proof of concept $ ./srm2_testGetRequestStatus srm://vmgdda0013.cern.ch:8446/ \' request status SRM_FAILURE request state 1 explanation: Failed for all tokens request summaryArray 1 ======= Begin Request ======== Request token: ' state[0]: 14 SRM_INTERNAL_ERROR $ Please read following dump of SRM log file: 07/23 18:01:50.330 9720,0 dpm_get_pending_req_by_token: mysql_query error: 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 07/23 18:01:50.330 9720,0 dpm_get_req_by_token: mysql_query error: 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 07/23 18:01:50.330 9720,0 GetRequestSummary: returns 0, statusCode=SRM_FAILURE Here is strace output from the SRMv2.2 process: poll([{fd=7, events=POLLIN|POLLPRI}], 1, 0) = 0 (Timeout) write(7, "\335\0\0\0\3SELECT \t\t R_ORDINAL, R_TOKEN, R_UID, \t\t R_GID, CLIENT_DN, CLIENTHOST, \t\t R_TYPE, U_TOKEN, \t\t FLAGS, RETRYTIME, NBREQFILES, \t\t CTIME, STIME, ETIME, \t\t STATUS, ERRSTRING, GROUPS \t\tFROM dpm_pending_req \t\tWHERE r_token = '''", 225) = 225 read(7, "\236\0\0\1\377(\4#42000You 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", 16384) = 162 gettimeofday({1311434508, 295920}, NULL) = 0 open("/var/log/srmv2.2/log", O_WRONLY|O_CREAT|O_APPEND, 0664) = 8 write(8, "07/23 17:21:48.295 9720,0 dpm_get_pending_req_by_token: mysql_query error: 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\n", 226) = 226 close(8) = 0 poll([{fd=7, events=POLLIN|POLLPRI}], 1, 0) = 0 (Timeout) write(7, "\325\0\0\0\3SELECT \t\t R_ORDINAL, R_TOKEN, R_UID, \t\t R_GID, CLIENT_DN, CLIENTHOST, \t\t R_TYPE, U_TOKEN, \t\t FLAGS, RETRYTIME, NBREQFILES, \t\t CTIME, STIME, ETIME, \t\t STATUS, ERRSTRING, GROUPS \t\tFROM dpm_req \t\tWHERE r_token = '''", 217) = 217 read(7, "\236\0\0\1\377(\4#42000You 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", 16384) = 162 gettimeofday({1311434508, 296597}, NULL) = 0 open("/var/log/srmv2.2/log", O_WRONLY|O_CREAT|O_APPEND, 0664) = 8 write(8, "07/23 17:21:48.296 9720,0 dpm_get_req_by_token: mysql_query error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use nea r ''''' at line 1\n", 218) = 218 close(8) = 0 gettimeofday({1311434508, 296942}, NULL) = 0 open("/var/log/srmv2.2/log", O_WRONLY|O_CREAT|O_APPEND, 0664) = 8 write(8, "07/23 17:21:48.296 9720,0 GetRequestSummary: returns 0, statusCode=SRM_FAILURE\n", 80) = 80 close(8) = 0 And here is strace from the MySQL process: read(31, "\3SELECT \t\t R_ORDINAL, R_TOKEN, R_UID, \t\t R_GID, CLIENT_DN , CLIENTHOST, \t\t R_TYPE, U_TOKEN, \t\t FLAGS, RETRYTIME, NBREQFILES, \t\t CTIME, STIME, ETIME, \t\t STATUS, ERRSTRING, GROUPS \t\tFROM dpm_pending_req \t\tWHERE r_token = '''", 221) = 221 rt_sigprocmask(SIG_BLOCK, ~[RTMIN RT_1], [HUP INT QUIT PIPE ALRM TERM TSTP], 8) = 0 rt_sigprocmask(SIG_SETMASK, [HUP INT QUIT PIPE ALRM TERM TSTP], NULL, 8) = 0 fcntl(31, F_SETFL, O_RDWR|O_NONBLOCK) = 0 time([1311436910]) = 1311436910 sched_setscheduler(15280, SCHED_OTHER, { 6 }) = -1 EINVAL (Invalid argument) sched_setscheduler(15280, SCHED_OTHER, { 8 }) = -1 EINVAL (Invalid argument) write(31, "\236\0\0\1\377(\4#42000You 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", 162) = 162 time([1311436910]) = 1311436910 read(31, 0x1f7ae9b0, 4) = -1 EAGAIN (Resource temporarily unavailable) time(NULL) = 1311436910 rt_sigprocmask(SIG_BLOCK, ~[RTMIN RT_1], [HUP INT QUIT PIPE ALRM TERM TSTP], 8) = 0 rt_sigprocmask(SIG_SETMASK, [HUP INT QUIT PIPE ALRM TERM TSTP], NULL, 8) = 0 fcntl(31, F_SETFL, O_RDWR) = 0 read(31, "\325\0\0\0", 4) = 4 read(31, "\3SELECT \t\t R_ORDINAL, R_TOKEN, R_UID, \t\t R_GID, CLIENT_DN , CLIENTHOST, \t\t R_TYPE, U_TOKEN, \t\t FLAGS, RETRYTIME, NBREQFILES, \t\t CTIME, STIME, ETIME, \t\t STATUS, ERRSTRING, GROUPS \t\tFROM dpm_req \t\tWHERE r_token = '''", 213) = 213 rt_sigprocmask(SIG_BLOCK, ~[RTMIN RT_1], [HUP INT QUIT PIPE ALRM TERM TSTP], 8) = 0 rt_sigprocmask(SIG_SETMASK, [HUP INT QUIT PIPE ALRM TERM TSTP], NULL, 8) = 0 fcntl(31, F_SETFL, O_RDWR|O_NONBLOCK) = 0 time([1311436910]) = 1311436910 sched_setscheduler(15280, SCHED_OTHER, { 6 }) = -1 EINVAL (Invalid argument) sched_setscheduler(15280, SCHED_OTHER, { 8 }) = -1 EINVAL (Invalid argument) write(31, "\236\0\0\1\377(\4#42000You 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", 162) = 162 time([1311436910]) = 1311436910 read(31, 0x1f7ae9b0, 4) = -1 EAGAIN (Resource temporarily unavailable) Affected Software: All versions of Disk Pool Manager (DPM) below 1.8.6 version are affected. 1.8.6 was released 19th of February 2013. Greets +) David Smith - for testing infrastructures and other helps not only at this topic. References 1) https://wiki.egi.eu/wiki/SVG:Advisory-SVG-2012-2683 2) http://site.pi3.com.pl/adv/disk_pool_manager_1.txt 3) http://blog.pi3.com.pl/?p=402 Timeline 2009-11-27 - Found vulnerability. 2011-08-03 - Vulnerability officialy reported. 2013-02-19 - Updated packages available in the EGI UMD-1 and EGI UMD-2. 2013-03-05 - Public disclosure on vendor's wiki, after allowing sites to upgrade (https://wiki.egi.eu/wiki/SVG:Advisory-SVG-2012-2683) 2013-03-10 - Release of this advisory. Best regards, Adam Zabrocki -- http://pi3.com.pl