Moodle 2.7.11+ (Build: 20151110)
I have a case where I run in to "Insecure dataroot Serious Your dataroot directory /var/www/moodledata is in the wrong location and might be exposed to the web" in > Site administration > Reports > Security overview. (Details are in the discussion "Insecure dataroot (yet again)" https://moodle.org/mod/forum/discuss.php?d=323171". I can summarize it here, if it helps.)
My question is how exactly the function is_dataroot_insecure diagnoses the problem. Here is the code:
===
549 $testfile = $CFG->dataroot.'/diag/public.txt';
550 if (!file_exists($testfile)) {
551 file_put_contents($testfile, 'test file, do not delete');
552 @chmod($testfile, $CFG->filepermissions);
553 }
554 $teststr = trim(file_get_contents($testfile));
555 if (empty($teststr)) {
556 // hmm, strange
557 return INSECURE_DATAROOT_WARNING;
558 }
559
560 $testurl = $datarooturl.'/diag/public.txt';
561 if (extension_loaded('curl') and
562 !(stripos(ini_get('disable_functions'), 'curl_init') !== FALSE) and
563 !(stripos(ini_get('disable_functions'), 'curl_setop') !== FALSE) and
564 ($ch = @curl_init($testurl)) !== false) {
565 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
566 curl_setopt($ch, CURLOPT_HEADER, false);
567 $data = curl_exec($ch);
568 if (!curl_errno($ch)) {
569 $data = trim($data);
570 if ($data === $teststr) {
571 curl_close($ch);
572 return INSECURE_DATAROOT_ERROR;
573 }
574 }
575 curl_close($ch);
576 }
577
578 if ($data = @file_get_contents($testurl)) {
579 $data = trim($data);
580 if ($data === $teststr) {
581 return INSECURE_DATAROOT_ERROR;
582 }
583 }
584
585 preg_match('|https?://([^/]+)|i', $testurl, $matches);
586 $sitename = $matches[1];
587 $error = 0;
588 if ($fp = @fsockopen($sitename, 80, $error)) {
589 preg_match('|https?://[^/]+(.*)|i', $testurl, $matches);
590 $localurl = $matches[1];
591 $out = "GET $localurl HTTP/1.1\r\n";
592 $out .= "Host: $sitename\r\n";
593 $out .= "Connection: Close\r\n\r\n";
594 fwrite($fp, $out);
595 $data = '';
596 $incoming = false;
597 while (!feof($fp)) {
598 if ($incoming) {
599 $data .= fgets($fp, 1024);
600 } else if (@fgets($fp, 1024) === "\r\n") {
601 $incoming = true;
602 }
603 }
604 fclose($fp);
605 $data = trim($data);
606 if ($data === $teststr) {
607 return INSECURE_DATAROOT_ERROR;
608 }
609 }
610
611 return INSECURE_DATAROOT_WARNING;
612 }
===
That functions creates a directory $moodledata/diag and a file $moodledata/diag/public.txt with known content, if they don't already exist - through PHP directly, not through a network socket.
Q1. Shouldn't it try to delete them first? Those files could have been created during an earlier try. It is possible that the administrator has changed something, file permissions for example, and trying again. But will fail due to the previous attempt.
Q2. It would be very serious if moodledata is _writable_ through the network. Shouldn't that be detected and ring an alarm?
Then from line 588 it tries to read that file through a connection "GET /moodledata//diag/public.txt HTTP/1.1 Host: example.com Connection: Close".
It reads the response in the while loop and does not enter the line 607, as expected. Still the function exists with a INSECURE_DATAROOT_WARNING due to line 611. That is the "serious" warning I am getting.
Q3. Isn't it a bug?
Observation 1: The $data in line 599 contains "Found The document has moved here (https://example.com/moodledata//diag/public.txt).
Observation 2: https://example.com/moodledata//diag/public.txt in the browser brings 404 Not found.
In reply to Visvanath Ratnaweera
Re: How does the function 'is_dataroot_insecure()' work?
by Visvanath Ratnaweera -
Timed out!
Debian 7.9
Apache/2.2.22 (Debian)
PHP Version 5.4.45-0+deb7u2
> Observation 2: https://example.com/moodledata//diag/public.txt in the browser brings 404 Not found.
https://example.com/moodledata/diag/public.txt (one / less) too brings 404 Not found.
Debian 7.9
Apache/2.2.22 (Debian)
PHP Version 5.4.45-0+deb7u2
> Observation 2: https://example.com/moodledata//diag/public.txt in the browser brings 404 Not found.
https://example.com/moodledata/diag/public.txt (one / less) too brings 404 Not found.