Table of Contents
Introduction #
To all the keen blog readers, you may know that we’ve had our fair share of rodeos with plugin ecosystems in the past. Besides minor or private ones that never made it onto this blog, we publicly looked at WordPress and Atlassian.
In this blog post, we’ll demonstrate our findings of yet another ecosystem, but this time with a spicy twist to it. We’ll show how YOU could explore the wild and audit plugin ecosystems yourself and what methodology to use. For each step, we’ll demonstrate how we applied it when it comes to Nextcloud / ownCloud.

Why Nextcloud / ownCloud? #
To answer the question in the title: Both are well-known software platforms and widely used. They’re used in professional environments but also in private home lab setups. We at cyllective use it ourselves, and many colleagues also run it privately. The open-source plugin community is quite big and it’s common that people have a few plugins installed.
Another question that might pop up in your head is why we look at Nextcloud and ownCloud simultaneously. They are both pretty similar and historically speaking, Nextcloud is a fork of ownCloud. They are so similar that many plugins can be used interchangeably, not all, but some can be installed in either of them without any issues.
A Pragmatic but Easy Approach - Our Four Steps #
With so many plugins available from the community and the vendor, it can initially feel overwhelming. At the same time, it’s just as easy to assume you can simply pick a few at random and jump right in.

To be able to audit plugin ecosystems properly we need a robust yet not overly complicated methodology that we can apply in nearly any case. For that we have a simple 4-step plan you must complete before actually auditing the plugins individually.

Note: In the following chapters code snippets will be shown for demonstration purposes. These snippets are from various different plugins and do not stem from a single one.
Scrape #
For our first step we need to scrape (all) the plugins in order to run a semi-automated analysis of each plugin. Depending on the parent software this can be easier said than done. In most cases, there is usually a storefront in the form of a website where you can download plugins and manually install them. If you’re very lucky, there is some type of API that lets you search for plugins, gather metadata, and finally download the files. If this is not the case, try to intercept the traffic when plugins get downloaded and installed and try to automate it using scripts (e.g. using Python).
In the case of Nextcloud and ownCloud both had an easily automatable REST API where we could parse plugin metadata (category, version, dependencies, etc.) and also download them.

After filling your disk with plugins, and ideally also setting up some type of database (in our case sometimes spreadsheets lol) to save the metadata for later steps, you’re basically ready to proceed to the next step.
Here is a quick overview of how many plugins we scraped when starting the research (20.06.2025):
| Platform | Total plugins | Maintained plugins |
|---|---|---|
| Nextcloud | 517 | 259 (~50%) |
| ownCloud | 94 | 94 (100%) |
Note: We arbitrarily set the definition of maintained. A plugin is deemed as maintained if it runs on the latest two major versions of the respective software. Plugins that were not maintained were filtered out
Analyze Plugins #
In order to efficiently audit plugins and the codebase, we need to get the maximum of information out of the raw files. This is where step two comes into play.
In most cases, you usually have some type of plugin descriptor file(s) which again contains plugin-specific metadata but usually also much more. For example, files that register endpoints/routes, files that register permissions, etc. In addition you can already look for certain patterns in the source code, without digging deep, that are relevant for auditing (Insecure Flags, Decorators, and Annotations).
All of that may sound abstract, so let’s look at how we applied exactly that to Nextcloud plugins. Here is a rough structure of the plugins after extracting the contents of the archive file.
.
├── appinfo
│ ├── info.xml
│ └── routes.php
├── css
│ └── js-dos.css
├── img
│ ├── app-dark.svg
│ └── app.svg
├── js
│ ├── doom.jsdos
│ ├── emulators
│ ├── js-dos.js
│ └── load-game.js
├── lib
│ ├── AppInfo
│ └── Controller
├── LICENSE
├── README.md
├── src
│ ├── App.vue
│ └── main.js
└── templates
└── index.php
Check out what we have here ;)
appinfo/info.xmlcontains metadata and information we’ll need later for sorting and prioritizingappinfo/routes.phpcontains the HTTP routes registered by the plugin
Some source code patterns that piqued our interest were the annotations used for the registered endpoints:
#[PublicPage]
#[NoCSRFRequired]
public function confirmResponse(...) {
...
}
#[NoCSRFRequired]
#[NoAdminRequired]
public function appointment(...) {
...
}
Sites with the #[PublicPage] are especially interesting because according to the Nextcloud Documentation ↗ they do not require authentication. Which, in the context of Nextcloud, is very interesting for attackers since Nextcloud instances are rarely open for registration.
After you have analyzed the Plugin Descriptor Files and potential Insecure Patterns, it’s time to store that data for later usage (Sorting and Prioritizing or for other means). A convenient way to do that is writing a parser to serialize all data you need into a handy format (e.g. JSON). In our case we wrote one in Golang and later used the output for our “database”.

Analyze Host #
Jumping into our next step we are nearly at the end of it before getting to the fun part (breaking stuff). This step is arguably one of the most important ones and you probably have done some checks before without being aware of it. You need to analyze the “host”, meaning looking at potential security measures of the parent system you’re installing the plugins into.
“Soft” Restrictions - Plugins may have some restrictions imposed by the parent system. For example, a plugin may first need to acquire permission from the user for certain actions or are limited either way.
“Hard” Restrictions - Usually plugins are written in some type of language, in rare cases there is a custom scripting language. You should check out, if plugins are restricted in using underlying APIs, or if they are basically free to do whatever the plugins language allows.
Identify “effective” vulnerabilities - There are plugins for all sorts of systems. So, depending on what the parent software is, you need to determine which types of vulnerabilities are likely to be effective. For example, for plugins in web-based systems you can focus on typical web vulnerabilities (e.g. XSS, SSRF, SQLi). If it’s some sort of native desktop application, then you should focus on a different category of vulnerabilities (e.g. Privilege Escalations/Elevation of privileges).
Let’s have a look at what we identified on Nextcloud / ownCloud. Their plugins can do basically everything, everywhere, all at once. There is no type of permission or role granting for plugins. There are no hard restrictions for either. But we do need to cut them some slack. If they would implement a permission system for plugins (which is not an easy thing) users would surely just grant everything out of convenience or laziness. We’ve seen that in other software. Also one could argue that the user is installing third-party code and should know all the risks that come with that.
Obviously both Nextcloud and ownCloud are web-based services, so we focus on web vulnerabilities. One prevalent vulnerability is XSS (Cross-Site Scripting) ↗. One measure that does not fix such a vulnerability but does mitigate the impact, is CSP (Content-Security Policy) ↗. We won’t go into detail about that but both implement a pretty solid one:
# Nextcloud
... script-src 'nonce-xxx;script-src-elem 'strict-dynamic' 'nonce-xxx'; ...
# ownCloud
... script-src 'self' 'unsafe-eval'; ...
Note: The value self is generally considered safe for the script-src directive. However, in the context of a file-sharing platform like ownCloud, this can be bypassed by uploading a malicious JavaScript file to your own files and then generating an anonymous public link to it.
buuuuuut…plugins can override that:
$policy = new ContentSecurityPolicy();
$policy->addAllowedScriptDomain(['\'unsafe-eval\'','\'unsafe-inline\'','\'unsafe-eval\'','\'script-src\'']);
Note: The CSP is not changed globally, but for individual pages provided by a plugin.
Sort & Prioritize #
Now we have arrived at the last and final step before we actually start hands-on hacking. Since it’s nearly impossible to look at each plugin meticulously, especially if you audit large scale ecosystems with a large contributing community, we need to start prioritizing. In this step we will “enrich” our spreadsheets and data we parsed earlier.
Check for public stats about the plugin. Check for install counts, GitHub stars or user ratings (be it negative or positive). Anything that has a lot of attention to it, is an indicator that people are using the software.
Familiarize yourself with the language the plugin is written in and identify patterns that could lead to security issues. (e.g.
shell_exec(),eval(),deserialize()). Searching for such patterns can be easily done using regex.Next, start searching for problematic framework‑specific patterns using regular expressions. For example, insecure method calls or overridden settings in the parent application. (e.g.
addAllowedScriptDomain).Lastly, use (free) code scanning tools to already determine low-hanging fruits and plugins that show a relatively high number of impactful vulnerabilities. Code scanners do often report many false positives or for us non meaningful vulnerabilities (e.g. use of MD5). Nevertheless, these tools can still uncover true positives. When a plugin displays a noticeably higher number of findings than the rest, it may be a strong indicator of insecure or inconsistent coding practices.
Now all of that might be again a lot to take in so let’s take look at a practical example on how we applied it onto Nextcloud / ownCloud.
Let’s first take a look at finding potential vulnerabilities by releasing your inner bash ninja.
Note: rg = ripgrep ↗ (grep on steroids). You can also see the use of the
-vflag for negative matches at end. We did this to exclude file paths of dependencies that which plugins used, since we do not want to audit 3rd-party code ;)
Finding SQL Injections
This script returns PHP file paths that contain SQL queries but lack any keywords indicating proper escaping or prepared statements within a defined line range, highlighting potential SQL injections.
files=$(rg -l -i "(SELECT|WHERE)" -g "*.php" .)
excl="(prepare|findEntity|createNamedParameter|IQueryBuilder|getQueryBuilder)"
for f in $(echo $files); do
res=$(rg "(SELECT|WHERE)" -B 5 -A 20 $f | tr -d '\n' | rg -v "$excl")
if [ ! -z $res ]; then
echo $f;
fi;
done | rg -v -i -e "(vendor|3rdparty)"
Finding PHP Specific Vulnerabilities
The two code snippets below show how to identify dangerous, language-specific calls/patterns. If, for example, the $_SERVER variable appears, it may indicate that the server is accessing user-controlled data. Similarly, because handling file uploads can do often go wrong, we looked for the appearance of the $_FILES variable. Lastly, insecure deserialization ↗ is common in languages such as PHP or Java, but it is not used very frequently in many applications. Therefore, we searched for files that call functions related to serialization or deserialization.
# request data
rg -g "*.php" '\$_SERVER' -l | grep -v -i -e "vendor|3rdparty"
# file uploads
rg -g "*.php" '\$_FILES' -l | grep -v -i -e "vendor|3rdparty"
rg -g "*.php" 'unserialize\s*\(|__wakeup\s*\(|__sleep\s*\(|__unserialize\s*\(|__serialize\s*\(' -l | grep -v -i -e "vendor|3rdparty"
Finding CSP-Header Override
Since plugins can override the Content Security Policy, as shown in the previous chapter, we looked for plugins that declare a new policy, which may indicate that XSS is possible.
rg -g "*.php" 'new ContentSecurityPolicy' -l | grep -v -i -e "vendor|3rdparty"
We also ran semgrep ↗, a free code scanning tool, on each plugin source code to gain insights. Plugins then got a higher prioritization if they had either
A.) Relative high count of vulnerabilities, compared to other plugins
B.) Critical vulnerabilities (e.g. code- or command execution) with semgrep reporting them at high “confidence”.
Auditing Plugins #
With the combination of our parsing, the previously shown sorting and prioritizing process and the results of semgrep, we had a list of plugins which we prioritized and looked at first. With all the boilerplate work done it was time to now do the hands-on work. This basically consisted of installing plugins to our sandbox instances and start dynamically testing, assisted with the source code. Besides that we also skimmed through the semgrep reports and tried to confirm findings found by the code scanner.


These graphs above show that semgrep reported around 2000 findings within the 500+ plugins and 200 vulnerabilities for the 94 plugins. As previously mentioned most of these are false positives, people who have worked with SAST tools (Static application security testing tools) know that. Nevertheless, you can see certain trends if you look at which categories have high counts of findings reported. This also helps when trying to look for promising vulnerability types (e.g. XSS).
The Results & Showcase #
To round things off, we’re sure you want to see the results of our research. Because of several factors, including still open vulnerabilities and ongoing coordination with multiple vendors and developers, some of whom did not agree to public disclosure, we decided not to fully disclose all identified vulnerabilities. Nevertheless, we would like to share selected highlights of the issues we uncovered.
| Count | Vulnerability Type | Notes |
|---|---|---|
| 4 | HTML Injection to XSS | 1x Unauthenticated |
| 3 | Arbitrary File Write | 1x Leveraged to Remote Code Execution |
| 2 | Local File Inclusion (LFI) / Server-Side Request Forgery (SSRF) | |
| 2 | SQL Injection | |
| 1 | HTML Injection | XSS denied by CSP :( |
| 1 | OS Command Injection |
SQL Injection #
Even though we’re not able to show you a lot, we think this specific vulnerability has a special meaning.

Looking at the screenshot, you might think this is an almost comically textbook SQL injection. Interestingly this was not detected by the SAST tools used. Therefore, this is the prime example of why automation is not everything, you need to put in the manual work.
HTML Injection #
In the stats overview you saw that there was one occasion where we had an HTML injection but we were not able to leverage it to a XSS. This is because the CSP-Header of Nextcloud does not allow inline JavaScript execution. This vulnerability occurred in a music management plugin in which so-called ID3 tags were not sanitized properly.


Unauthenticated XSS #
This vulnerability is a textbook XSS. Unfortunately, we cannot show you the actual code, but here is a pseudo-code representation of it:
echo "something something " . $userInput . " something something";

What makes this one stand out is the attack vector. The plugin exposed a #[PublicPage] endpoint (i.e., no authentication) that handled both incoming and outgoing SMS text messages. Because the message content was directly used within the output, an attacker could potentially pwn a Nextcloud user through a text message.
Wrapping Up #
To wrap up, we hope you learned something, and we’re looking forward to future blog posts about others researching plugin ecosystems, there’s still much more to explore (we see you NTC ↗ ✌️). In our opinion, doing research in these ecosystems is a way of giving back to the open-source community. It supports the amazing software people build and helps keep these ecosystems safe.
As a small side note looking ahead in the future: Nextcloud is now supporting ExApps ↗, short for “External Apps”, which allow plugins to be written in practically any language and run inside Docker containers. It’s likely that more plugins will move to this model in the future, which would unveil a totally new ecosystem worth exploring.
