Christian Karlsson
Sök
  • Hem
  • Fackligt
  • Linux
  • Politik
  • Mastodon


Tema: WP ÖppenFramtid (wp-oppenframtid)


inc/import.php:48

Filtyp validerades enbart via filändelse (.csv). En angripare kan byta namn på en körbar fil till .csv och ladda upp den via importverktyget.

$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
if ($ext !== 'csv') { ... }
✅ Fix: mime_content_type() används nu mot vitlistan [text/plain, text/csv, application/csv, application/octet-stream]. Filändelse och MIME-typ måste båda godkännas.

OWASP A03:2021 – Injection · OWASP File Upload Cheat Sheet


inc/terms.php:96

Identisk problematik som TEMA-SEC-007 – enbart filändelse (.json) kontrollerades.

$ext = strtolower(pathinfo($_FILES['terms_file']['name'], PATHINFO_EXTENSION));
if ($ext !== 'json') { ... }
✅ Fix: mime_content_type() används nu mot vitlistan [application/json, text/plain, application/octet-stream]. Filändelse och MIME-typ måste båda godkännas.

OWASP A03:2021 – Injection · OWASP File Upload Cheat Sheet


inc/integrity.php:196

Filuppladdning av audit-JSON-filer godkändes baserat på klient-side accept-attribut och filändelse. Ingen server-side MIME-kontroll gjordes.

// Ingen MIME-kontroll, enbart storlek och JSON-parsning
✅ Fix: mime_content_type() kontrolleras nu mot vitlistan [application/json, text/plain, application/octet-stream] innan filen parsas. Ogiltiga filer avvisas med felmeddelande.

OWASP A03:2021 – Injection · OWASP File Upload Cheat Sheet


Tema: WP ÖppenFramtid (wp-oppenframtid)
Plugin: Antispam Bee
Plugin: Open Messages (security-audit.json)


front-page.php:24

setcookie() för 'blogtree_visited' saknar säkerhetsflaggorna HttpOnly (hindrar JS-åtkomst), Secure (kräver HTTPS) och SameSite (skyddar mot CSRF). Kakan är visserligen funktionell och innehåller ingen känslig data, men mönstret bör följa best practice.

setcookie('blogtree_visited', time(), time() + 365 * DAY_IN_SECONDS, '/');
✅ Fix: setcookie() ersatt med options-array med secure: true, httponly: true, samesite: Lax. Kräver PHP 7.3+.

OWASP A05:2021 – Security Misconfiguration · RFC 6265bis SameSite


inc/maintenance.php:107

str_contains() introducerades i PHP 8.0. Om servern kör PHP 7.x kraschar underhållsfunktionen med ett fatalt fel, vilket kan blockera hela webbplatsen.

if (str_contains($path, $slug)) return;
✅ Fix: str_contains() ersatt med strpos() !== false – kompatibelt med PHP 7.x och uppåt.

PHP Compatibility


inc/integrity.php:52

file_get_contents() körs på den uppladdade temporära filen utan att kontrollera filstorleken. En administratör (eller komprometterat admin-konto) kan ladda upp en extremt stor JSON-fil som orsakar minnesproblem.

$content = file_get_contents($tmp);
✅ Fix: Storlekskontroll tillagd före file_get_contents(): filer > 512 KB avvisas med felmeddelande.

OWASP A05:2021 – Security Misconfiguration


inc/comments.php:17

wp_ajax_blogtree_post_comment saknar begränsning på antal kommentarer per användare eller tidsperiod. En inloggad användare kan spamma obegränsat antal kommentarer och fylla databasen.

add_action('wp_ajax_blogtree_post_comment', 'blogtree_handle_comment');
✅ Fix: Transient-baserad räknare per user_id: max 10 kommentarer per timme. Avvisar med felmeddelande vid överskridning.

OWASP A05:2021 – Security Misconfiguration


inc/community.php:12

blogtree_handle_submission saknar begränsning på hur många inlägg en inloggad användare kan skicka in. En användare kan skicka in hundratals inlägg och belasta databasen.

add_action('wp_ajax_blogtree_submit_post', 'blogtree_handle_submission');
✅ Fix: Transient-baserad räknare per user_id: max 3 insändare per 24 timmar. Avvisar med felmeddelande vid överskridning.

OWASP A05:2021 – Security Misconfiguration


front-page.php:152

get_avatar_url(1) och get_avatar(1, …) förutsätter att administratören alltid har user ID 1. Om webbplatsen migreras eller admin-kontot skapas om kan fel bild visas eller anrop misslyckas.

echo esc_url(get_avatar_url(1, ['size' => 200]));
✅ Fix: Admin-ID hämtas dynamiskt via get_user_by('email', get_option('admin_email')). Fallback till ID 1 om ingen träff. Båda get_avatar() och get_avatar_url() använder $_admin_id.


inc/likes.php:22

blogtree_liked_by sparar en array av alla user IDs som gillat ett inlägg. Om WordPress REST API är aktiverat kan denna meta vara tillgänglig publikt. User IDs kan i kombination med profil-URL kopplas till identifierbara personer.

$liked_by = (array) get_post_meta($post_id, 'blogtree_liked_by', true);
✅ Fix: register_post_meta() för blogtree_liked_by och blogtree_likes med show_in_rest: false och auth_callback: __return_false. Skyddar mot REST API-exponering.

GDPR Art. 4(1) – Definition of personal data · WordPress REST API Handbook


inc/follow.php:1

blogtree_followed_topics sparas i user meta men ingen hook på delete_user rensar datan. Vid kontoborttagning finns kvarliggande data i wp_usermeta-tabellen.

update_user_meta($user_id, 'blogtree_followed_topics', $followed);
✅ Fix: delete_user-hook registrerad i follow.php: delete_user_meta($user_id, 'blogtree_followed_topics') körs automatiskt vid kontoborttagning.

GDPR Art. 17 – Right to erasure


inc/likes.php:1

User IDs i blogtree_liked_by rensas inte när ett konto raderas. En raderad användares ID kvarstår i post meta och kan kopplas till anonymiserat eller återanvänt konto.

// Ingen delete_user-hook registrerad
✅ Fix: delete_user-hook i likes.php: söker via SQL efter alla inlägg där user_id finns i blogtree_liked_by, tar bort ID:t och uppdaterar räknaren.

GDPR Art. 17 – Right to erasure


inc/avatars.php:12

Om en användare inte laddat upp en lokal avatar skickas en MD5-hash av e-postadressen till gravatar.com. Gravatar kan använda detta för spårning. Temat har visserligen ett lokalt avatar-system men det är frivilligt.

if (!$attachment_id) return $args; // Faller tillbaka till Gravatar
✅ Fix: pre_get_avatar_data-filtret sätter alltid found_avatar=true. Saknas lokal avatar används assets/img/avatar-default.svg (lokal silhuett). Gravatar kontaktas aldrig.

GDPR Art. 5(1)(c) – Data minimisation · Gravatar Privacy Policy


antispam-bee/

Antispam Bee analyserar spam lokalt utan att skicka kommentarsdata till externa servrar (till skillnad från Akismet). Ingen persondata lämnar servern vid grundläggande spamkontroll. Godkänt.

Antispam Bee GitHub – Privacy Statement


antispam-bee/

Antispam Bee kan logga IP-adressen för spammade kommentarer i spam-loggen. IP-adresser klassas som personuppgifter enligt GDPR. Inställningen 'Anonymisera IP-adresser' finns inte i Antispam Bee 6.9.4.

✅ Fix: Inställningen saknas i Antispam Bee 6.9.4. Skräppost raderas automatiskt efter 7 dagar (konfigurerat). IP-adresser behandlas tillfälligt och lagras max 7 dagar – dokumenteras i integritetspolicyn som berättigat intresse (GDPR Art. 6(1)(f)).

GDPR Art. 4(1) – Personal data · CJEU C-582/14 Breyer – IP addresses


antispam-bee/

Om landbaserad blockering aktiveras i Antispam Bee behandlas besökarens IP-adress för att avgöra geografiskt ursprung. Detta är behandling av personuppgifter och kräver rättslig grund (t.ex. berättigat intresse).

✅ Fix: Landbaserad blockering är inte aktiverad — ingen IP-behandling för geolokalisering sker. Ej tillämplig.

GDPR Art. 6(1)(f) – Legitimate interest


antispam-bee/

Antispam Bees honeypot-fält har ett relativt statiskt klassnamn. Sofistikerade bots som analyserar källkoden kan identifiera och hoppa över fältet.

✅ Fix: Flerlagersskydd aktiverat: Tidsbaserad kontroll, BBCode-filter, Reguljära uttryck och Lokal skräppostdatabas är alla aktiva.

Antispam Bee Settings Documentation


antispam-bee/

Antispam Bee är GPL-licensierat och underhålls av WordPress-communityt. Koden är publik på GitHub och kan granskas av vem som helst. Godkänt.

github.com/pluginkollektiv/antispam-bee


includes/class-om-form.php:57

Kontaktformuläret samlar in namn, e-postadress och meddelande och sparar dem i databasen utan att informera besökaren eller begära uttryckligt samtycke. Strider mot GDPR Artikel 6 och 13.

// Ingen samtyckescheckbox eller integritetspolicylänk i render_shortcode()
✅ Fix: Obligatorisk samtyckescheckbox tillagd med lagringstid och länk till integritetspolicy. Servervalidering av om_consent i handle_submission(). Kolumn consent_at DATETIME tillagd i om_messages.

GDPR Art. 6 – Lawfulness of processing · GDPR Art. 13 – Information to be provided


includes/class-om-cleanup.php:1

Pluginet erbjuder ingen mekanism för att på begäran radera en specifik persons data. Automatisk 30-dagarsrensning uppfyller inte kravet på manuell hantering av raderingsbegäran.

// Ingen hook, admin-åtgärd eller sökning på e-postadress för manuell radering
✅ Fix: OM_Privacy::erase_user_data() implementerad och kopplad till register_personal_data_eraser(). Admin-sökformulär med sök- och raderingsfunktion per e-postadress. Radering loggas i audit-loggen.

GDPR Art. 17 – Right to erasure


includes/

Pluginet registrerar inte register_personal_data_exporter(). En registrerad person kan inte exportera sina uppgifter via WordPress inbyggda verktyg.

// register_personal_data_exporter() saknas helt
✅ Fix: OM_Privacy::export_user_data() implementerad med paginering. Exporterar från_namn, e-post, ämne, meddelande, datum och consent_at.

GDPR Art. 20 – Right to data portability · WordPress Privacy API


wp-openmessages.php:1

Pluginet registrerar inte sig i WordPress inbyggda Privacy API. Webbplatsägare kan inte använda WordPress inbyggda GDPR-verktyg för att hantera förfrågningar.

// register_personal_data_eraser() och register_personal_data_exporter() saknas
✅ Fix: Ny OM_Privacy-klass registrerar båda hooks. Tillgängligt under Verktyg → Integritet i WordPress admin.

GDPR Art. 17, Art. 20 · WordPress Privacy API Handbook


includes/class-om-cleanup.php:89

Meddelande-ID:n sammanfogas med implode() och interpoleras direkt i en SQL-sträng utan $wpdb->prepare(). Även om intval() används är mönstret riskabelt.

$wpdb->query( "UPDATE {$wpdb->prefix}om_messages SET warn_sent = 1 WHERE id IN ($ids)" );
✅ Fix: Ersatt med foreach-loop som anropar $wpdb->update() per ID med typade format-specifiers (%d).

OWASP A03:2021 – Injection · WordPress Coding Standards – DB.PreparedSQL


includes/class-om-crypto.php:10

Om AUTH_KEY inte är definierat används strängen 'om-fallback-salt-change-me' som krypteringsnyckel. Denna kända sträng exponeras i källkoden och ger inget skydd.

$salt = defined( 'AUTH_KEY' ) ? AUTH_KEY : 'om-fallback-salt-change-me';
✅ Fix: Fallback-strängen borttagen. Kastar RuntimeException om AUTH_KEY saknas eller är kortare än 32 tecken.

OWASP A02:2021 – Cryptographic Failures


includes/class-om-crypto.php:19

AES-256-CBC utan HMAC eller GCM-läge ger inget integritetsskydd. En angripare med databasåtkomst kan modifiera chiffertexten utan att dekrypteringen misslyckas.

openssl_encrypt( $plaintext, 'aes-256-cbc', self::key(), OPENSSL_RAW_DATA, $iv );
✅ Fix: Bytt till AES-256-GCM med 12-byte IV och 16-byte GCM-tag. Lagringsformat: base64(IV[12] + TAG[16] + ciphertext).

OWASP A02:2021 – Cryptographic Failures · CWE-326 Inadequate Encryption Strength


includes/class-om-form.php:106

Inga begränsningar per IP eller session hindrar en angripare från att skicka tusentals formulärmeddelanden.

// Ingen kontroll av antal inskickningar per IP/tidsenhet
✅ Fix: check_rate_limit() implementerad med transient-baserad räknare per anonymiserad IP. Max 5 inskickningar per 10 minuter.

OWASP A05:2021 – Security Misconfiguration


includes/class-om-form.php:175

Besökarens IP-adress lagras som MD5-hash i transient-nyckeln. IP-adresser är personuppgifter enligt GDPR. MD5 av en känd IP är trivial att reversa.

return substr( md5( $_SERVER['REMOTE_ADDR'] ?? 'x' ), 0, 12 );
✅ Fix: IPv4: sista oktetten nollställs. IPv6: de sista 64 bitarna nollställs. Hash saltas med wp_salt(). MD5 ersatt med SHA-256.

GDPR Art. 4(1) – Definition of personal data · CJEU C-582/14 Breyer


includes/class-om-database.php:177

IMAP/SMTP-lösenord lagras krypterade i WordPress-databasen. Vid en fullständig databaskompromettering kan lösenorden dekrypteras om angriparen också känner till AUTH_KEY.

$data['password_enc'] = OM_Crypto::encrypt( $data['password'] );
✅ Fix: README uppdaterat med sektion 'Skydda wp-config.php': flytta filen ovanför web root, filrättigheter (640), Apache-blockering, starka salts och krav på AUTH_KEY ≥ 32 tecken.

OWASP A02:2021 – Cryptographic Failures · CWE-312 Cleartext Storage of Sensitive Information


admin/views/settings.php:8

edit_imap och edit_smtp GET-parametrar saknar nonce-verifiering. En angripare kan lura en inloggad admin att öppna en redigerings-URL via länk.

$edit_imap = isset( $_GET['edit_imap'] ) ? OM_Database::get_imap_account( (int) $_GET['edit_imap'] ) : null;
✅ Fix: wp_verify_nonce() tillagd med konto-specifik action. Redigera-länkarna genereras med wp_nonce_url().

OWASP A01:2021 – Broken Access Control · CWE-352 CSRF


includes/class-om-admin.php:60

Det finns ingen audit-logg över vilken admin-användare som läste, svarade på, raderade eller stjärnmärkte ett meddelande.

// Ingen loggning av get_current_user_id() vid känsliga åtgärder
✅ Fix: om_audit_log-tabell skapad. Åtgärder loggas: read, reply, delete, star. Raderingsbegäran loggar hashad e-post.

OWASP A09:2021 – Security Logging and Monitoring Failures


includes/class-om-captcha.php:12

session_start() anropas utan session_regenerate_id() efter CAPTCHA-validering. Risk för session fixation.

if ( ! session_id() ) { session_start(); }
✅ Fix: session_regenerate_id(true) anropas efter lyckad validering. CAPTCHA-token migrerat till WordPress-transient. session_start() borttaget.

OWASP A07:2021 – Identification and Authentication Failures · CWE-384 Session Fixation


includes/class-om-admin.php:286

$_SERVER['HTTP_REFERER'] används som redirect-destination utan whitelist-validering. wp_safe_redirect() stoppar externa domäner men skyddar inte mot intern URL-manipulation.

$url = $_SERVER['HTTP_REFERER'] ?? admin_url( 'admin.php?page=open-messages' );
✅ Fix: Referrern saneras med sanitize_url() + wp_unslash(). Valideras mot admin_url(). Query-parametrar saneras med sanitize_key() och sanitize_text_field().

OWASP A01:2021 – Broken Access Control


includes/class-om-imap.php:58

Kontrollen om ett imap_uid redan finns och efterföljande INSERT är inte atomisk. Två parallella WP-Cron-körningar kan importera samma e-post dubbelt.

$exists = $wpdb->get_var(...);
if ( $exists ) { continue; }
// INSERT här – race window
✅ Fix: UNIQUE KEY imap_uid_account tillagd. SELECT+INSERT ersatt med atomisk INSERT IGNORE via $wpdb->prepare().

CWE-362 Race Condition


admin/views/compose.php:37

reply_to och reply_subject från $_GET skyddas med esc_attr() i output men saniteras inte vid inläsning. Längdvalidering saknas.

value="<?php echo esc_attr( $_GET['reply_to'] ?? '' ); ?>"
✅ Fix: $prefill_to saneras med sanitize_email(). $prefill_subject saneras med sanitize_text_field() och begränsas till 255 tecken.

OWASP A03:2021 – Injection


includes/class-om-imap.php:31

error_log() anropas med kontonamn och ID vid misslyckad dekryptering. Risk att lösenord loggas av framtida ändringar.

error_log( "[Open Messages] Kunde inte dekryptera lösenord för IMAP-konto #{$account->id}" );
✅ Fix: Ny OM_Logger-klass skapad. Alla error_log()-anrop ersatta med OM_Logger::error/info/warning(). Mönsterfilter blockerar meddelanden med nyckelord som 'password', 'secret', 'token'.

OWASP A09:2021 – Security Logging and Monitoring Failures · CWE-532 Insertion of Sensitive Information into Log File


includes/class-om-form.php:89

Honeypot-fältet döljs med CSS-klassen om-hp men är synligt i HTML-källkoden med klassnamnet 'om-hp'. Sofistikerade bots kan känna igen och hoppa över det.

<div class="om-hp" aria-hidden="true">
✅ Fix: Fältnamnet genereras med wp_generate_uuid4() per sidladdning och lagras i ett transient (10 min). Döljs med inline-style. Single-use: transientet raderas efter verifiering.


includes/class-om-database.php:59

replied_body lagras permanent i om_messages-tabellen utan möjlighet att radera enbart ett svar. Strider mot GDPR-principen om dataminimering.

replied_body  LONGTEXT,
replied_at    DATETIME,
✅ Fix: Ny tabell om_replies (id, message_id, body, sent_by, created_at). replied_body/replied_at borttagna ur om_messages. Individuell raderingsknapp per svar i admin.

GDPR Art. 5(1)(c) – Data minimisation


includes/class-om-form.php:57

Besökaren informeras inte om att deras meddelande lagras i 30 dagar. Lagringstiden framgår inte i formuläret.

// Ingen information om lagringstid i formulärets HTML
✅ Fix: Lagringstid visas dynamiskt under samtyckescheckboxen baserat på konstanten OM_RETENTION_DAYS (standardvärde 30). Länk till integritetspolicyn hämtas automatiskt.

GDPR Art. 13(2)(a) – Storage period

Christian Karlsson

© 2026 Christian Karlsson

https://christiankarlsson.xyz/author/christiankarlssonxyz/ https://christiankarlsson.xyz/?author=1 https://christiankarlsson.xyz/ https://christiankarlsson.xyz https://christiankarlsson.xyz/?author=0